From 9ee3a62d4768ab80ff7767ffe5abd3c30db8b5cc Mon Sep 17 00:00:00 2001 From: setavenger Date: Thu, 24 Apr 2025 20:57:05 +0200 Subject: [PATCH 1/8] little cleanup --- pkg/networking/nwc.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/networking/nwc.go b/pkg/networking/nwc.go index a5113ad..235133b 100644 --- a/pkg/networking/nwc.go +++ b/pkg/networking/nwc.go @@ -33,13 +33,6 @@ type GetBalanceResponse struct { Balance int64 `json:"balance"` // in millisatoshis } -const ( - privKeyWalletService = "66d1069f89c77d846cca006d132f2493a0acd31f650e000bc5199306556d8b21" - pubKeyWalletService = "3e6221b76a6555c819c8d0c87d9af47ce1b31c0b413f276b2f0d8ddd2a3e1c1f" - privKeyClientSecret = "125b81e4b8b21c889374ebf1a4b786791f0088a4f3b64260e75fd759497c2700" - pubKeyClient = "e89262ebbe2b114bfa1f53b0b65a46a9bdf65ac2c11a7969933c9aac8a4e83dd" -) - // publishInfoEvent publishes a replaceable info event (kind 13194) to the relay. func publishInfoEvent(ctx context.Context, relay *nostr.Relay, privKey, pubKey string) { // Supported commands as a space-separated string. From 204943d532a1e2a1391d0b9f4cd86146dec06399 Mon Sep 17 00:00:00 2001 From: setavenger Date: Fri, 25 Apr 2025 14:29:29 +0200 Subject: [PATCH 2/8] non privacy mode still working; privacy mode still in deadlock --- cmd/blindbit-scan/main.go | 102 ++++-------------------- internal/config/load.go | 6 +- internal/config/privatemode.go | 13 +++ internal/config/vars.go | 19 ++++- internal/daemon/daemon.go | 34 ++++---- internal/daemon/scan.go | 9 +-- internal/server/routes.go | 60 +++++++------- internal/server/server.go | 6 +- internal/startup/base.go | 105 +++++++++++++++++++++++++ internal/startup/private.go | 69 ++++++++++++++++ internal/startup/simple.go | 67 ++++++++++++++++ pkg/database/db.go | 140 ++++++++++++++++++++++++++++----- 12 files changed, 474 insertions(+), 156 deletions(-) create mode 100644 internal/config/privatemode.go create mode 100644 internal/startup/base.go create mode 100644 internal/startup/private.go create mode 100644 internal/startup/simple.go diff --git a/cmd/blindbit-scan/main.go b/cmd/blindbit-scan/main.go index c1024a6..cde1471 100644 --- a/cmd/blindbit-scan/main.go +++ b/cmd/blindbit-scan/main.go @@ -1,103 +1,31 @@ package main import ( - "bytes" - "context" - "flag" "os" "os/signal" - "github.com/setavenger/blindbit-scan/internal/config" - "github.com/setavenger/blindbit-scan/internal/daemon" - nwcserver "github.com/setavenger/blindbit-scan/internal/nwc_server" - "github.com/setavenger/blindbit-scan/internal/server" - "github.com/setavenger/blindbit-scan/pkg/database" - "github.com/setavenger/blindbit-scan/pkg/logging" - "github.com/setavenger/blindbit-scan/pkg/networking/nwc" + "github.com/setavenger/blindbit-scan/internal/startup" ) -func init() { - // todo can this double reference work? - flag.StringVar( - &config.DirectoryPath, - "datadir", - config.DefaultDirectoryPath, - "Set the base directory for blindbit-scan. Default directory is ~/.blindbit-scan.", - ) - flag.Parse() -} +// func init() { +// // todo can this double reference work? +// flag.StringVar( +// &config.DirectoryPath, +// "datadir", +// config.DefaultDirectoryPath, +// "Set the base directory for blindbit-scan. Default directory is ~/.blindbit-scan.", +// ) + +// flag.BoolVar(&config.PrivateMode, "private", false, "BlindBit Scan will run in private mode. All data on disk will be encrypted all data will only be decrypted in memory. Upon restart the unlock endpoint needs to be called to decrypt data and start the scanning.") + +// flag.Parse() +// } func main() { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) - var err error - - // todo move to a go routine to avoid blocking - err = config.SetupConfigs(config.DirectoryPath) - if err != nil { - logging.L.Panic().Err(err). - Msg("startup failed, could not setup configs") - } - - var d *daemon.Daemon - - d, err = daemon.SetupDaemonNoWallet() - if err != nil { - logging.L.Panic().Err(err). - Msg("startup failed, could produce daemon hull") - } - w, err := database.TryLoadWalletFromDisk(config.PathDbWallet) - if err != nil { - logging.L.Warn().Err(err). - Msg("startup failed, could setup full daemon") - } - d.Wallet = w - - // Setup BlindBit Nostr Wallet Connect - nwcServer := nwcserver.NewNwcServer(d) - - logging.L.Info().Msg("attempting to load NWC apps from disk") - controller, err := database.TryLoadingControllerFromDisk(context.Background(), config.PathDbNWC) - if err != nil { - logging.L.Panic().Err(err).Msg("failed to create new controller") - } - logging.L.Trace().Any("apps", controller.Apps()).Msg("controller data") - - controller.RegisterHandler(nwc.GET_INFO_METHOD, nwcServer.GetInfoHandler()) - controller.RegisterHandler(nwc.GET_BALANCE_METHOD, nwcServer.GetBalanceHandler()) - controller.RegisterHandler(nwc.LIST_UTXOS_METHOD, nwcServer.ListUtxosHandler()) - - err = controller.ConnectRelay() - if err != nil { - logging.L.Panic().Err(err).Msg("failed to connect to relay") - } - go controller.StartListening() - - // http server - go func() { - err = server.StartNewServer(d, controller) - if err != nil { - logging.L.Panic().Err(err). - Msg("startup failed, could start server") - } - }() - - // when we exit we still flush the last state - defer d.SaveWalletToDB() - go func() { - // if the keys are not setup we wait - if d.Wallet == nil || bytes.Equal(d.Wallet.SecretKeyScan[:], make([]byte, 32)) || bytes.Equal(d.Wallet.PubKeySpend[:], make([]byte, 33)) { - logging.L.Info().Msg("waiting for keys") - <-config.KeysReadyChan - d, err = daemon.SetupDaemon(config.PathDbWallet) - if err != nil { - logging.L.Panic().Err(err). - Msg("startup failed, could setup full daemon") - } - } - go d.ContinuousScan() - }() + startup.RunProgram() // wait for program stop signal <-interrupt diff --git a/internal/config/load.go b/internal/config/load.go index 5d937d4..d1d0060 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -49,6 +49,7 @@ func LoadConfigs(pathToConfig string) (err error) { viper.BindEnv("auth.pass", "AUTH_PASS") viper.BindEnv("log_level", "LOG_LEVEL") + viper.BindEnv("privacy_mode", "PRIVACY_MODE") // app seed is for umbrel inputs // viper.BindEnv("external_app_seed", "EXTERNAL_APP_SEED") @@ -68,6 +69,7 @@ func LoadConfigs(pathToConfig string) (err error) { viper.SetDefault("wallet.birth_height", 840000) viper.SetDefault("log_level", "info") + viper.SetDefault("privacy_mode", false) // app seed // viper.SetDefault("external_app_seed", "") // we normally don't use it @@ -90,11 +92,13 @@ func LoadConfigs(pathToConfig string) (err error) { AutomaticScanInterval = 1 * time.Minute } + PrivateMode = viper.GetBool("privacy_mode") + // Basic Auth Data AuthUser = viper.GetString("auth.user") AuthPass = viper.GetString("auth.pass") - if AuthUser == "" || AuthPass == "" { + if (AuthUser == "" || AuthPass == "") && !PrivateMode { err := errors.New("config is missing auth settings") slog.Error(err.Error()) return err diff --git a/internal/config/privatemode.go b/internal/config/privatemode.go new file mode 100644 index 0000000..7e929c9 --- /dev/null +++ b/internal/config/privatemode.go @@ -0,0 +1,13 @@ +package config + +var ( + UnlockChan = make(chan string) +) + +func init() { + UnlockChan = make(chan string) +} + +func Unlock() { + close(UnlockChan) +} diff --git a/internal/config/vars.go b/internal/config/vars.go index 3f5d413..3400a81 100644 --- a/internal/config/vars.go +++ b/internal/config/vars.go @@ -6,11 +6,24 @@ import ( "github.com/btcsuite/btcd/chaincfg" ) +type PrivateModeSetup struct { + ScanSecretKey [32]byte + SpendPubKey [33]byte + BirthHeight uint64 + LabelCount int + Password string + BasicAuthUser string + BasicAuthPass string +} + func init() { KeysReadyChan = make(chan struct{}) + PrivateModeSetupChan = make(chan PrivateModeSetup) } var ( + PrivateMode bool + // ExposeHttpHost if set gRPC will be exposed via http and not unix socket. This variable also defines the where it will be exposed. ExposeHttpHost string @@ -41,7 +54,9 @@ var ( SpendPubKey [33]byte - BirthHeight uint64 + // A reasonable default birth height + // Silent Payments was not used a lot before that + BirthHeight uint64 = 890000 LabelCount int @@ -52,4 +67,6 @@ var ( // keys are ready chan KeysReadyChan chan struct{} + + PrivateModeSetupChan chan PrivateModeSetup ) diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index ef555dc..7e0fc58 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -22,38 +22,36 @@ type Daemon struct { Wallet *wallet.Wallet NewBlockChan <-chan *electrum.SubscribeHeadersResult TriggerRescanChan chan uint64 + DBWriter *database.DBWriter } // Will try to load a wallet from disk or will create a new one based on the blindbit.toml config-file -func SetupDaemon(path string) (*Daemon, error) { +func (d *Daemon) SetupExternalClients() (err error) { clientBlindBit := networking.ClientBlindBit{BaseUrl: config.BlindBitServerAddress} var clientElectrum *electrum.Client - var err error if config.UseElectrum { logging.L.Info().Msg("connecting to Electrum server") clientElectrum, err = networking.CreateElectrumClient(config.ElectrumServerAddress, config.ElectrumTorProxyHost) if err != nil { logging.L.Err(err).Msg("") - return nil, err + return err } } - w, err := database.TryLoadWalletFromDisk(path) - if err != nil { - logging.L.Err(err).Msg("") - return nil, err - } - d, err := NewDaemon(w, &clientBlindBit, clientElectrum) - if err != nil { - logging.L.Err(err).Msg("") - return nil, err - } + d.ClientBlindBit = &clientBlindBit + d.ClientElectrum = clientElectrum - return d, err + return nil } -func NewDaemon(wallet *wallet.Wallet, clientBlindBit *networking.ClientBlindBit, clientElectrum *electrum.Client) (*Daemon, error) { +func NewDaemon( + wallet *wallet.Wallet, + clientBlindBit *networking.ClientBlindBit, + clientElectrum *electrum.Client, +) ( + *Daemon, error, +) { var channel <-chan *electrum.SubscribeHeadersResult var err error if config.UseElectrum { @@ -106,6 +104,10 @@ func SetupDaemonNoWallet() (*Daemon, error) { return d, err } +func (d *Daemon) SetDbWriter(dbWriter *database.DBWriter) { + d.DBWriter = dbWriter +} + // ResetDaemonAndWallet deletes the stored wallet DB // used when new keys are added such that scanning continues from scratch func (d *Daemon) ResetDaemonAndWallet() (err error) { @@ -128,5 +130,5 @@ func (d *Daemon) Cancel() { } func (d *Daemon) SaveWalletToDB() (err error) { - return database.WriteWalletToDB(config.PathDbWallet, d.Wallet) + return d.DBWriter.WriteWalletToDB(config.PathDbWallet, d.Wallet) } diff --git a/internal/daemon/scan.go b/internal/daemon/scan.go index fa33bda..bd3339a 100644 --- a/internal/daemon/scan.go +++ b/internal/daemon/scan.go @@ -13,7 +13,6 @@ import ( "github.com/btcsuite/btcd/btcutil/gcs/builder" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/setavenger/blindbit-scan/internal/config" - "github.com/setavenger/blindbit-scan/pkg/database" "github.com/setavenger/blindbit-scan/pkg/logging" "github.com/setavenger/blindbit-scan/pkg/networking" "github.com/setavenger/blindbit-scan/pkg/utils" // todo move blindbitd/src to a pkg for all blindbit programs @@ -255,7 +254,7 @@ func (d *Daemon) SyncToTip(chainTip uint64) error { if i%100 == 0 { // do some writes anyways to save the last state of the scan height - err = database.WriteWalletToDB(config.PathDbWallet, d.Wallet) + err = d.DBWriter.WriteWalletToDB(config.PathDbWallet, d.Wallet) if err != nil { logging.L.Err(err).Uint64("height", i).Msg("") return err @@ -276,7 +275,7 @@ func (d *Daemon) SyncToTip(chainTip uint64) error { } // todo: database should be an interface to allow other forms of storing data. - err = database.WriteWalletToDB(config.PathDbWallet, d.Wallet) + err = d.DBWriter.WriteWalletToDB(config.PathDbWallet, d.Wallet) if err != nil { logging.L.Err(err).Msg("") return err @@ -493,7 +492,7 @@ func (d *Daemon) ForceSyncFrom(fromHeight uint64) error { } if i%100 == 0 { // do some writes anyways to save the last state of the scan height - err = database.WriteWalletToDB(config.PathDbWallet, d.Wallet) + err = d.DBWriter.WriteWalletToDB(config.PathDbWallet, d.Wallet) if err != nil { logging.L.Err(err).Msg("") return err @@ -511,7 +510,7 @@ func (d *Daemon) ForceSyncFrom(fromHeight uint64) error { // todo: make more robust, unnecessary trick d.Wallet.LastScanHeight = i } - err = database.WriteWalletToDB(config.PathDbWallet, d.Wallet) + err = d.DBWriter.WriteWalletToDB(config.PathDbWallet, d.Wallet) if err != nil { logging.L.Err(err).Msg("") return err diff --git a/internal/server/routes.go b/internal/server/routes.go index 312399d..8784288 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -1,15 +1,12 @@ package server import ( - "bytes" "encoding/hex" "fmt" "net/http" - "time" "github.com/gin-gonic/gin" "github.com/setavenger/blindbit-scan/internal/config" - "github.com/setavenger/blindbit-scan/pkg/database" "github.com/setavenger/blindbit-scan/pkg/logging" "github.com/setavenger/blindbit-scan/pkg/wallet" "github.com/setavenger/go-bip352" @@ -63,11 +60,12 @@ func (s *Server) PostRescan(c *gin.Context) { } type SetupReq struct { - ScanSecret string `json:"secret_sec"` + ScanSecret string `json:"scan_secret"` SpendPublic string `json:"spend_pub"` BirthHeight uint `json:"birth_height"` } +// todo: fix block after calling while sync is running func (s *Server) PutSilentPaymentKeys(c *gin.Context) { var err error @@ -88,7 +86,11 @@ func (s *Server) PutSilentPaymentKeys(c *gin.Context) { c.Abort() return } - viper.Set("wallet.scan_secret_key", keys.ScanSecret) + if len(scanSecret) != 32 { + c.JSON(http.StatusInternalServerError, gin.H{"err": "scan secret must be 32 bytes"}) + c.Abort() + return + } spendPub, err := hex.DecodeString(keys.SpendPublic) if err != nil { @@ -96,13 +98,21 @@ func (s *Server) PutSilentPaymentKeys(c *gin.Context) { c.Abort() return } - viper.Set("wallet.spend_pub_key", keys.SpendPublic) - viper.Set("wallet.birth_height", keys.BirthHeight) + if len(spendPub) != 33 { + c.JSON(http.StatusInternalServerError, gin.H{"err": "spend public key must be 33 bytes"}) + c.Abort() + return + } // we only write if nothing before failed if keys.BirthHeight < 1 { keys.BirthHeight = 1 } + + viper.Set("wallet.scan_secret_key", keys.ScanSecret) + viper.Set("wallet.spend_pub_key", keys.SpendPublic) + viper.Set("wallet.birth_height", keys.BirthHeight) + config.BirthHeight = uint64(keys.BirthHeight) config.ScanSecretKey = bip352.ConvertToFixedLength32(scanSecret) config.SpendPubKey = bip352.ConvertToFixedLength33(spendPub) @@ -116,17 +126,22 @@ func (s *Server) PutSilentPaymentKeys(c *gin.Context) { return } - go func() { - if s.Daemon.Wallet == nil || bytes.Equal(s.Daemon.Wallet.SecretKeyScan[:], make([]byte, 32)) || bytes.Equal(s.Daemon.Wallet.PubKeySpend[:], make([]byte, 33)) { - config.KeysReadyChan <- struct{}{} - } - }() + // go func() { + // if s.Daemon.Wallet == nil || bytes.Equal(s.Daemon.Wallet.SecretKeyScan[:], make([]byte, 32)) || bytes.Equal(s.Daemon.Wallet.PubKeySpend[:], make([]byte, 33)) { + // config.KeysReadyChan <- struct{}{} + // } + // }() var newWallet *wallet.Wallet // logging.L.Trace().Any("birth", config.BirthHeight).Any("l-count", config.LabelCount).Any("scan", config.ScanSecretKey).Any("spend", config.SpendPubKey).Msg("config info") - newWallet, err = wallet.SetupWallet(config.BirthHeight, config.LabelCount, config.ScanSecretKey, config.SpendPubKey) + newWallet, err = wallet.SetupWallet( + config.BirthHeight, + config.LabelCount, + config.ScanSecretKey, + config.SpendPubKey, + ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"err": err.Error()}) c.Abort() @@ -143,25 +158,16 @@ func (s *Server) PutSilentPaymentKeys(c *gin.Context) { s.Daemon.Wallet = newWallet - // logging.L.Debug().Any("wallet", s.Daemon.Wallet).Msg("") - - go func() { - <-time.After(5 * time.Second) - err = s.Daemon.ContinuousScan() - if err != nil { - logging.L.Err(err).Msg("") - return - } - }() - - // logging.L.Trace().Any("wallet", s.Daemon.Wallet).Msg("") - address, err := s.Daemon.Wallet.GenerateAddress() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"err": err.Error()}) c.Abort() return } + + // no routine to alert the caller if something is wrong + config.KeysReadyChan <- struct{}{} + c.JSON(http.StatusOK, gin.H{"address": address}) } @@ -173,7 +179,7 @@ func (s *Server) NewNwcConnection(c *gin.Context) { return } - err = database.WriteNip47ControllerToDB(config.PathDbNWC, s.Nip47Controller) + err = s.Daemon.DBWriter.WriteNip47ControllerToDB(config.PathDbNWC, s.Nip47Controller) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"err": err.Error()}) c.Abort() diff --git a/internal/server/server.go b/internal/server/server.go index b670e75..0855fcf 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -44,7 +44,11 @@ func (s *Server) RunServer() error { config.AuthUser: config.AuthPass, })) - router.PUT("/new-keys", s.PutSilentPaymentKeys) + // we only allow this in simple mode + // too much complexity in private mode + if !config.PrivateMode { + router.PUT("/new-keys", s.PutSilentPaymentKeys) + } // BlindBit adaptation of Nostr Wallet Connect router.POST("/new-nwc-connection", s.NewNwcConnection) diff --git a/internal/startup/base.go b/internal/startup/base.go new file mode 100644 index 0000000..bb09bfc --- /dev/null +++ b/internal/startup/base.go @@ -0,0 +1,105 @@ +package startup + +import ( + "context" + "flag" + "os" + "os/signal" + + "github.com/setavenger/blindbit-scan/internal/config" + "github.com/setavenger/blindbit-scan/internal/daemon" + nwcserver "github.com/setavenger/blindbit-scan/internal/nwc_server" + "github.com/setavenger/blindbit-scan/internal/server" + "github.com/setavenger/blindbit-scan/pkg/logging" + "github.com/setavenger/blindbit-scan/pkg/networking/nwc" +) + +func init() { + // todo can this double reference work? + flag.StringVar( + &config.DirectoryPath, + "datadir", + config.DefaultDirectoryPath, + "Set the base directory for blindbit-scan. Default directory is ~/.blindbit-scan.", + ) + + flag.BoolVar(&config.PrivateMode, "private", false, "BlindBit Scan will run in private mode. All data on disk will be encrypted all data will only be decrypted in memory. Upon restart the unlock endpoint needs to be called to decrypt data and start the scanning.") + + flag.Parse() +} + +func RunProgram() { + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + var err error + + err = config.SetupConfigs(config.DirectoryPath) + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could not setup configs") + } + + var d *daemon.Daemon + + if config.PrivateMode { + logging.L.Info().Msg("running in privacy mode") + logging.L.Info().Msg("privacy mode requires a setup api call to setup the instance") + d, err = StartupWithPrivateMode() + } else { + d, err = StartupWithSimpleMode() + } + + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could not setup daemon") + } + + // Setup BlindBit Nostr Wallet Connect + nwcServer := nwcserver.NewNwcServer(d) + + logging.L.Info().Msg("attempting to load NWC apps from disk") + controller, err := d.DBWriter.TryLoadingControllerFromDisk(context.Background(), config.PathDbNWC) + if err != nil { + logging.L.Panic().Err(err).Msg("failed to create new controller") + } + logging.L.Trace().Any("apps", controller.Apps()).Msg("controller data") + + controller.RegisterHandler(nwc.GET_INFO_METHOD, nwcServer.GetInfoHandler()) + controller.RegisterHandler(nwc.GET_BALANCE_METHOD, nwcServer.GetBalanceHandler()) + controller.RegisterHandler(nwc.LIST_UTXOS_METHOD, nwcServer.ListUtxosHandler()) + + err = controller.ConnectRelay() + if err != nil { + logging.L.Panic().Err(err).Msg("failed to connect to relay") + } + go controller.StartListening() + + // http server + go func() { + err = server.StartNewServer(d, controller) + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could start server") + } + }() + + // when we exit we still flush the last state + defer d.SaveWalletToDB() + + go func() { + // if the wallet is not setup we wait for the keys ready signal + if d.Wallet == nil { + logging.L.Info().Msg("waiting for keys") + <-config.KeysReadyChan + err = d.SetupExternalClients() + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could setup full daemon") + } + } + go d.ContinuousScan() + }() + + // wait for program stop signal + <-interrupt +} diff --git a/internal/startup/private.go b/internal/startup/private.go new file mode 100644 index 0000000..25bd0c1 --- /dev/null +++ b/internal/startup/private.go @@ -0,0 +1,69 @@ +package startup + +import ( + "github.com/setavenger/blindbit-scan/internal" + "github.com/setavenger/blindbit-scan/internal/config" + "github.com/setavenger/blindbit-scan/internal/daemon" + "github.com/setavenger/blindbit-scan/pkg/database" + "github.com/setavenger/blindbit-scan/pkg/logging" + "github.com/setavenger/blindbit-scan/pkg/wallet" +) + +func StartupWithPrivateMode() (d *daemon.Daemon, err error) { + if internal.CheckIfFileExists(config.PathDbWallet) { + // we need to load the existing instance, wait for unlock api call + d, err = SetupExistingInstancePrivateMode() + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could produce daemon hull") + } + } else { + // we need to setup a whole new instance and wait for Setup rest api call + d, err = SetupNewInstancePrivateMode() + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could produce daemon hull") + } + } + + return d, nil +} + +func SetupExistingInstancePrivateMode() (d *daemon.Daemon, err error) { + password := <-config.UnlockChan + d, err = daemon.SetupDaemonNoWallet() + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could only produce daemon hull") + } + + d.SetDbWriter(&database.DBWriter{Password: password}) + + w, err := d.DBWriter.LoadWalletFromDisk(config.PathDbWallet) + if err != nil { + logging.L.Warn().Err(err). + Msg("startup failed, could setup full daemon") + } + d.Wallet = w + + return d, nil +} + +func SetupNewInstancePrivateMode() (d *daemon.Daemon, err error) { + setup := <-config.PrivateModeSetupChan + d, err = daemon.SetupDaemonNoWallet() + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could only produce daemon hull") + } + + d.Wallet = &wallet.Wallet{ + SecretKeyScan: setup.ScanSecretKey, + PubKeySpend: setup.SpendPubKey, + BirthHeight: setup.BirthHeight, + } + + d.SetDbWriter(&database.DBWriter{Password: setup.Password}) + + return d, nil +} diff --git a/internal/startup/simple.go b/internal/startup/simple.go new file mode 100644 index 0000000..5ea040b --- /dev/null +++ b/internal/startup/simple.go @@ -0,0 +1,67 @@ +package startup + +import ( + "github.com/setavenger/blindbit-scan/internal" + "github.com/setavenger/blindbit-scan/internal/config" + "github.com/setavenger/blindbit-scan/internal/daemon" + "github.com/setavenger/blindbit-scan/pkg/database" + "github.com/setavenger/blindbit-scan/pkg/logging" + "github.com/setavenger/blindbit-scan/pkg/wallet" +) + +func StartupWithSimpleMode() (d *daemon.Daemon, err error) { + if internal.CheckIfFileExists(config.PathDbWallet) { + d, err = SetupExistingInstanceSimpleMode() + } else { + d, err = SetupNewInstanceSimpleMode() + } + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could only produce daemon hull") + } + + return d, nil +} + +func SetupNewInstanceSimpleMode() (d *daemon.Daemon, err error) { + // no password needed, so we just do the old process + d, err = daemon.SetupDaemonNoWallet() + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could only produce daemon hull") + } + + // no password needed, so we just set it to empty string + d.SetDbWriter(&database.DBWriter{Password: ""}) + + if config.ScanSecretKey != [32]byte{} && config.SpendPubKey != [33]byte{} { + w := &wallet.Wallet{ + SecretKeyScan: config.ScanSecretKey, + PubKeySpend: config.SpendPubKey, + BirthHeight: config.BirthHeight, + } + d.Wallet = w + } + return d, nil +} + +func SetupExistingInstanceSimpleMode() (d *daemon.Daemon, err error) { + // no password needed, so we just do the old process + d, err = daemon.SetupDaemonNoWallet() + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could only produce daemon hull") + } + + // no password needed, so we just set it to empty string + d.SetDbWriter(&database.DBWriter{Password: ""}) + + w, err := d.DBWriter.LoadWalletFromDisk(config.PathDbWallet) + if err != nil { + logging.L.Warn().Err(err). + Msg("startup failed, could setup full daemon") + } + d.Wallet = w + + return d, nil +} diff --git a/pkg/database/db.go b/pkg/database/db.go index e46dd72..40e0466 100644 --- a/pkg/database/db.go +++ b/pkg/database/db.go @@ -2,6 +2,12 @@ package database import ( "context" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "errors" + "fmt" + "io" "os" "github.com/setavenger/blindbit-scan/internal" @@ -16,6 +22,109 @@ type Serialiser interface { DeSerialise([]byte) error } +type DBWriter struct { + Password string +} + +func (w *DBWriter) WriteToDB(path string, dataStruct Serialiser) error { + if config.PrivateMode { + data, err := dataStruct.Serialise() + if err != nil { + logging.L.Err(err).Msg("failed to serialise data") + return err + } + data, err = EncryptData(data, w.Password) + if err != nil { + logging.L.Err(err).Msg("failed to encrypt data") + return err + } + err = os.WriteFile(path, data, 0644) + if err != nil { + logging.L.Err(err).Msg("failed to write to db") + return err + } + return nil + } + return WriteToDB(path, dataStruct) +} + +func (w *DBWriter) ReadFromDB(path string, dataStruct Serialiser) error { + if config.PrivateMode { + data, err := os.ReadFile(path) + if err != nil { + logging.L.Err(err).Msg("failed to read from db") + return err + } + data, err = DecryptData(data, w.Password) + if err != nil { + logging.L.Err(err).Msg("failed to decrypt data") + return err + } + return dataStruct.DeSerialise(data) + } + return ReadFromDB(path, dataStruct) +} + +func (w *DBWriter) EncryptData(data []byte) ([]byte, error) { + return EncryptData(data, w.Password) +} + +// AES GCM encryption function +func EncryptData(data []byte, password string) ([]byte, error) { + // Create a new AES cipher block using the password + block, err := aes.NewCipher([]byte(password)) + if err != nil { + return nil, err + } + + // Create a new GCM cipher mode + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + // Generate a random nonce + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + + // Encrypt the data using GCM + ciphertext := gcm.Seal(nonce, nonce, data, nil) + return ciphertext, nil +} + +func DecryptData(data []byte, password string) ([]byte, error) { + // Create a new AES cipher block using the password + block, err := aes.NewCipher([]byte(password)) + if err != nil { + return nil, err + } + + // Create a new GCM cipher mode + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + // Get the nonce size + nonceSize := gcm.NonceSize() + if len(data) < nonceSize { + return nil, fmt.Errorf("ciphertext too short") + } + + // Extract nonce from ciphertext + nonce, ciphertext := data[:nonceSize], data[nonceSize:] + + // Decrypt the data + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + + return plaintext, nil +} + func WriteToDB(path string, dataStruct Serialiser) error { data, err := dataStruct.Serialise() if err != nil { @@ -47,37 +156,32 @@ func ReadFromDB(path string, dataStruct Serialiser) error { return nil } -func WriteWalletToDB(p string, w *wallet.Wallet) error { - if w == nil { - // do nothting +func (w *DBWriter) WriteWalletToDB(p string, wallet *wallet.Wallet) error { + if wallet == nil { + // do nothing logging.L.Warn().Msg("wallet was nil") return nil } - return WriteToDB(p, w) + return w.WriteToDB(p, wallet) } -func WriteNip47ControllerToDB(p string, c *nwc.Nip47Controller) error { - return WriteToDB(p, c) +func (w *DBWriter) WriteNip47ControllerToDB(p string, c *nwc.Nip47Controller) error { + return w.WriteToDB(p, c) } -func TryLoadWalletFromDisk(path string) (*wallet.Wallet, error) { +func (w *DBWriter) LoadWalletFromDisk(path string) (*wallet.Wallet, error) { if internal.CheckIfFileExists(path) { - var w wallet.Wallet - err := ReadFromDB(path, &w) - return &w, err + var wallet wallet.Wallet + err := w.ReadFromDB(path, &wallet) + return &wallet, err } logging.L.Trace().Str("path", path).Msg("No wallet data on disk") - return wallet.SetupWallet( - config.BirthHeight, - config.LabelCount, - config.ScanSecretKey, - config.SpendPubKey, - ) + return nil, errors.New("no wallet data on disk") } -func TryLoadingControllerFromDisk( +func (w *DBWriter) TryLoadingControllerFromDisk( ctx context.Context, path string, ) ( @@ -86,7 +190,7 @@ func TryLoadingControllerFromDisk( ) { if internal.CheckIfFileExists(path) { var apps nwc.Apps - err = ReadFromDB(path, &apps) + err = w.ReadFromDB(path, &apps) if err != nil { logging.L.Err(err).Msg("failed to load apps from db") return nil, err From 71157e3341e510d206094452a693e041ff5abfaf Mon Sep 17 00:00:00 2001 From: setavenger Date: Fri, 25 Apr 2025 22:21:34 +0200 Subject: [PATCH 3/8] mostly working, some bug with scan key still exists on the address computation side --- go.mod | 4 +- go.sum | 8 ++- internal/config/auth.go | 38 ++++++++++ internal/config/paths.go | 4 +- internal/daemon/daemon.go | 20 ++++++ internal/server/middleware.go | 28 ++++++++ internal/server/routes.go | 127 ++++++++++++++++++++++++++++++++-- internal/server/server.go | 32 +++++++-- internal/startup/base.go | 54 ++++++--------- internal/startup/private.go | 38 +++++++++- pkg/database/db.go | 26 +++++-- pkg/types/auth.go | 16 +++++ 12 files changed, 342 insertions(+), 53 deletions(-) create mode 100644 internal/config/auth.go create mode 100644 internal/server/middleware.go create mode 100644 pkg/types/auth.go diff --git a/go.mod b/go.mod index a4d08c9..80a1e0c 100644 --- a/go.mod +++ b/go.mod @@ -10,12 +10,14 @@ require ( github.com/btcsuite/goleveldb v1.0.0 github.com/gin-contrib/cors v1.7.2 github.com/gin-gonic/gin v1.10.0 + github.com/google/uuid v1.6.0 github.com/nbd-wtf/go-nostr v0.50.0 github.com/rs/zerolog v1.33.0 github.com/setavenger/blindbitd v0.0.0-20240602183715-c4e971bba3e4 github.com/setavenger/go-bip352 v0.1.7 github.com/setavenger/go-electrum v1.1.1 github.com/spf13/viper v1.19.0 + golang.org/x/text v0.24.0 ) require ( @@ -28,6 +30,7 @@ require ( github.com/coder/websocket v1.8.12 // indirect github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -69,7 +72,6 @@ require ( golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/net v0.37.0 // indirect golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect google.golang.org/protobuf v1.36.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 7670f05..c5547b7 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 h1:aYo8nnk3ojoQkP5iErif5Xxv0Mo0Ga/FR5+ffl/7+Nk= +github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -92,6 +94,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -242,8 +246,8 @@ golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/config/auth.go b/internal/config/auth.go new file mode 100644 index 0000000..19b8965 --- /dev/null +++ b/internal/config/auth.go @@ -0,0 +1,38 @@ +// internal/config/auth.go +package config + +import ( + "crypto/rand" + + "github.com/btcsuite/btcd/btcutil/base58" + petname "github.com/dustinkirkland/golang-petname" + "github.com/setavenger/blindbit-scan/pkg/logging" + "github.com/setavenger/blindbit-scan/pkg/types" +) + +var ( + authCredentials *types.AuthCredentials +) + +func GenerateAuthCredentials() *types.AuthCredentials { + username := petname.Generate(2, "-") + randomBytes := make([]byte, 8) + if _, err := rand.Read(randomBytes); err != nil { + logging.L.Panic().Err(err).Msg("failed to generate random bytes") + return nil + } + password := base58.Encode(randomBytes) + + return &types.AuthCredentials{ + Username: username, + Password: password, + } +} + +func SetAuthCredentials(creds *types.AuthCredentials) { + authCredentials = creds +} + +func GetAuthCredentials() *types.AuthCredentials { + return authCredentials +} diff --git a/internal/config/paths.go b/internal/config/paths.go index 10bc881..90a3877 100644 --- a/internal/config/paths.go +++ b/internal/config/paths.go @@ -10,6 +10,7 @@ var ( PathConfig string PathDbWallet string PathDbNWC string + PathDbAuth string ) // needed for the flag default @@ -20,6 +21,7 @@ const PathEndingConfig = "/blindbit.toml" const PathEndingWallet = dataPath + "/wallet" const PathEndingNWC = dataPath + "/nwc" const PathEndingKeys = dataPath + "/keys" +const PathEndingBasicAuth = dataPath + "/basic_auth" func SetPaths(baseDirectory string) { if baseDirectory != "" { @@ -34,7 +36,7 @@ func SetPaths(baseDirectory string) { PathConfig = DirectoryPath + PathEndingConfig PathDbWallet = DirectoryPath + PathEndingWallet PathDbNWC = DirectoryPath + PathEndingNWC - + PathDbAuth = DirectoryPath + PathEndingBasicAuth // create the directories utils.TryCreateDirectoryPanic(DirectoryPath) diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 7e0fc58..d991455 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -9,6 +9,7 @@ import ( "github.com/setavenger/blindbit-scan/pkg/database" "github.com/setavenger/blindbit-scan/pkg/logging" "github.com/setavenger/blindbit-scan/pkg/networking" // todo move all blindbitd/src/* + "github.com/setavenger/blindbit-scan/pkg/types" "github.com/setavenger/blindbit-scan/pkg/wallet" "github.com/setavenger/go-electrum/electrum" ) @@ -22,6 +23,7 @@ type Daemon struct { Wallet *wallet.Wallet NewBlockChan <-chan *electrum.SubscribeHeadersResult TriggerRescanChan chan uint64 + AuthCredentials *types.AuthCredentials DBWriter *database.DBWriter } @@ -130,5 +132,23 @@ func (d *Daemon) Cancel() { } func (d *Daemon) SaveWalletToDB() (err error) { + if d.Wallet == nil { + return nil + } return d.DBWriter.WriteWalletToDB(config.PathDbWallet, d.Wallet) } + +func (d *Daemon) LoadAuthCredentials() error { + var creds types.AuthCredentials + if err := d.DBWriter.ReadFromDB(config.PathDbAuth, &creds); err != nil { + return err + } + d.AuthCredentials = &creds + config.SetAuthCredentials(&creds) + return nil +} + +func (d *Daemon) Unlock(password string) error { + d.DBWriter = &database.DBWriter{Password: password} + return d.LoadAuthCredentials() +} diff --git a/internal/server/middleware.go b/internal/server/middleware.go new file mode 100644 index 0000000..6b9da00 --- /dev/null +++ b/internal/server/middleware.go @@ -0,0 +1,28 @@ +package server + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/setavenger/blindbit-scan/internal/config" +) + +// internal/server/middleware.go +func (s *Server) basicAuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + creds := config.GetAuthCredentials() + if creds == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Wallet not unlocked"}) + c.Abort() + return + } + + username, password, ok := c.Request.BasicAuth() + if !ok || username != creds.Username || password != creds.Password { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + c.Abort() + return + } + c.Next() + } +} diff --git a/internal/server/routes.go b/internal/server/routes.go index 8784288..a37e159 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/setavenger/blindbit-scan/internal/config" + "github.com/setavenger/blindbit-scan/pkg/database" "github.com/setavenger/blindbit-scan/pkg/logging" "github.com/setavenger/blindbit-scan/pkg/wallet" "github.com/setavenger/go-bip352" @@ -126,12 +127,6 @@ func (s *Server) PutSilentPaymentKeys(c *gin.Context) { return } - // go func() { - // if s.Daemon.Wallet == nil || bytes.Equal(s.Daemon.Wallet.SecretKeyScan[:], make([]byte, 32)) || bytes.Equal(s.Daemon.Wallet.PubKeySpend[:], make([]byte, 33)) { - // config.KeysReadyChan <- struct{}{} - // } - // }() - var newWallet *wallet.Wallet // logging.L.Trace().Any("birth", config.BirthHeight).Any("l-count", config.LabelCount).Any("scan", config.ScanSecretKey).Any("spend", config.SpendPubKey).Msg("config info") @@ -188,3 +183,123 @@ func (s *Server) NewNwcConnection(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"uri": nwcURI}) } + +// UnlockReq represents the request to unlock the wallet +type UnlockReq struct { + Password string `json:"password"` +} + +// UnlockResponse represents the response from unlocking the wallet +type UnlockResponse struct { + Success bool `json:"success"` + Error string `json:"error,omitempty"` +} + +func (s *Server) Unlock(c *gin.Context) { + var req UnlockReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + return + } + + // todo: several instances of password being passed around + // todo: this should be refactored + dbWriter := database.DBWriter{ + Password: req.Password, + } + + s.Daemon.SetDbWriter(&dbWriter) + + // Decrypt wallet data + if err := s.Daemon.Unlock(req.Password); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unlock wallet"}) + return + } + + // Load auth credentials + if err := s.Daemon.LoadAuthCredentials(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load auth credentials"}) + return + } + + config.UnlockChan <- req.Password + + c.JSON(http.StatusOK, UnlockResponse{Success: true}) +} + +type SetupInstanceReq struct { + ScanSecret string `json:"scan_secret"` + SpendPublic string `json:"spend_pub"` + BirthHeight uint `json:"birth_height"` + Password string `json:"password"` +} + +// SetupInstance is used to setup the instance for the first time +// it will generate a new set of auth credentials and save them to the database +// the keys and the unlock password have to be sent in the request body +func (s *Server) SetupInstance(c *gin.Context) { + var req SetupInstanceReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + return + } + + // load keys + scanSecret, err := hex.DecodeString(req.ScanSecret) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"err": err.Error()}) + c.Abort() + return + } + if len(scanSecret) != 32 { + c.JSON(http.StatusInternalServerError, gin.H{"err": "scan secret must be 32 bytes"}) + c.Abort() + return + } + + spendPub, err := hex.DecodeString(req.SpendPublic) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"err": err.Error()}) + c.Abort() + return + } + if len(spendPub) != 33 { + c.JSON(http.StatusInternalServerError, gin.H{"err": "spend public key must be 33 bytes"}) + c.Abort() + return + } + + setup := config.PrivateModeSetup{ + ScanSecretKey: [32]byte(scanSecret), + SpendPubKey: [33]byte(spendPub), + BirthHeight: uint64(req.BirthHeight), + Password: req.Password, + } + + config.PrivateModeSetupChan <- setup + + creds := config.GenerateAuthCredentials() + config.SetAuthCredentials(creds) + // Wait for auth credentials to be generated and loaded + if creds == nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate auth credentials"}) + return + } + + tempWriter := database.DBWriter{ + Password: req.Password, + } + + // Save auth credentials to disk + if err := tempWriter.SaveAuthCredentials(creds); err != nil { + logging.L.Error().Err(err).Msg("Failed to save auth credentials") + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save auth credentials"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "username": creds.Username, + "password": creds.Password, + }) +} diff --git a/internal/server/server.go b/internal/server/server.go index 0855fcf..e3410e5 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,14 +1,15 @@ package server import ( - "log/slog" "net/http" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/setavenger/blindbit-scan/internal/config" "github.com/setavenger/blindbit-scan/internal/daemon" + "github.com/setavenger/blindbit-scan/pkg/logging" "github.com/setavenger/blindbit-scan/pkg/networking/nwc" + "github.com/setavenger/blindbitd/src/utils" ) func StartNewServer( @@ -40,16 +41,35 @@ func (s *Server) RunServer() error { AllowCredentials: true, })) - router.Use(gin.BasicAuth(gin.Accounts{ - config.AuthUser: config.AuthPass, - })) - // we only allow this in simple mode // too much complexity in private mode if !config.PrivateMode { router.PUT("/new-keys", s.PutSilentPaymentKeys) } + // setup instance no auth + if config.PrivateMode { + // setup instance + router.POST("/setup-instance", func(ctx *gin.Context) { + authExists := utils.CheckIfFileExists(config.PathDbAuth) + walletExists := utils.CheckIfFileExists(config.PathDbWallet) + + if authExists || walletExists { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "instance already (partially) setup"}) + ctx.Abort() + return + } + ctx.Next() + }, s.SetupInstance) + + // unlock the wallet + // this is used to unlock the wallet when the user has already setup the instance + // no basic auth because basic auth is decrypted with the password + router.POST("/unlock", s.Unlock) + } + + router.Use(s.basicAuthMiddleware()) + // BlindBit adaptation of Nostr Wallet Connect router.POST("/new-nwc-connection", s.NewNwcConnection) @@ -71,7 +91,7 @@ func (s *Server) RunServer() error { walletReadyGroup.POST("/rescan", s.PostRescan) if err := router.Run(config.ExposeHttpHost); err != nil { - slog.Error(err.Error()) + logging.L.Err(err).Msg("failed to start server") return err } return nil diff --git a/internal/startup/base.go b/internal/startup/base.go index bb09bfc..a01410c 100644 --- a/internal/startup/base.go +++ b/internal/startup/base.go @@ -1,14 +1,12 @@ package startup import ( - "context" "flag" "os" "os/signal" "github.com/setavenger/blindbit-scan/internal/config" "github.com/setavenger/blindbit-scan/internal/daemon" - nwcserver "github.com/setavenger/blindbit-scan/internal/nwc_server" "github.com/setavenger/blindbit-scan/internal/server" "github.com/setavenger/blindbit-scan/pkg/logging" "github.com/setavenger/blindbit-scan/pkg/networking/nwc" @@ -40,39 +38,13 @@ func RunProgram() { } var d *daemon.Daemon - - if config.PrivateMode { - logging.L.Info().Msg("running in privacy mode") - logging.L.Info().Msg("privacy mode requires a setup api call to setup the instance") - d, err = StartupWithPrivateMode() - } else { - d, err = StartupWithSimpleMode() - } - + d, err = daemon.NewDaemon(nil, nil, nil) if err != nil { logging.L.Panic().Err(err). Msg("startup failed, could not setup daemon") } - // Setup BlindBit Nostr Wallet Connect - nwcServer := nwcserver.NewNwcServer(d) - - logging.L.Info().Msg("attempting to load NWC apps from disk") - controller, err := d.DBWriter.TryLoadingControllerFromDisk(context.Background(), config.PathDbNWC) - if err != nil { - logging.L.Panic().Err(err).Msg("failed to create new controller") - } - logging.L.Trace().Any("apps", controller.Apps()).Msg("controller data") - - controller.RegisterHandler(nwc.GET_INFO_METHOD, nwcServer.GetInfoHandler()) - controller.RegisterHandler(nwc.GET_BALANCE_METHOD, nwcServer.GetBalanceHandler()) - controller.RegisterHandler(nwc.LIST_UTXOS_METHOD, nwcServer.ListUtxosHandler()) - - err = controller.ConnectRelay() - if err != nil { - logging.L.Panic().Err(err).Msg("failed to connect to relay") - } - go controller.StartListening() + var controller = &nwc.Nip47Controller{} // http server go func() { @@ -84,9 +56,29 @@ func RunProgram() { }() // when we exit we still flush the last state - defer d.SaveWalletToDB() + defer func() { + if d != nil { + d.SaveWalletToDB() + } + }() go func() { + var d2 *daemon.Daemon + if config.PrivateMode { + logging.L.Info().Msg("running in privacy mode") + logging.L.Info().Msg("privacy mode requires a setup api call to setup the instance") + d2, err = StartupWithPrivateMode(controller) + } else { + d2, err = StartupWithSimpleMode() + } + + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could not setup daemon") + } + + *d = *d2 + // if the wallet is not setup we wait for the keys ready signal if d.Wallet == nil { logging.L.Info().Msg("waiting for keys") diff --git a/internal/startup/private.go b/internal/startup/private.go index 25bd0c1..75191e2 100644 --- a/internal/startup/private.go +++ b/internal/startup/private.go @@ -1,17 +1,25 @@ package startup import ( + "context" + "github.com/setavenger/blindbit-scan/internal" "github.com/setavenger/blindbit-scan/internal/config" "github.com/setavenger/blindbit-scan/internal/daemon" + nwcserver "github.com/setavenger/blindbit-scan/internal/nwc_server" "github.com/setavenger/blindbit-scan/pkg/database" "github.com/setavenger/blindbit-scan/pkg/logging" + "github.com/setavenger/blindbit-scan/pkg/networking/nwc" "github.com/setavenger/blindbit-scan/pkg/wallet" ) -func StartupWithPrivateMode() (d *daemon.Daemon, err error) { - if internal.CheckIfFileExists(config.PathDbWallet) { +func StartupWithPrivateMode(nip47Controller *nwc.Nip47Controller) (d *daemon.Daemon, err error) { + walletExists := internal.CheckIfFileExists(config.PathDbWallet) + authExists := internal.CheckIfFileExists(config.PathDbAuth) + + if walletExists && authExists { // we need to load the existing instance, wait for unlock api call + logging.L.Info().Msg("Waiting for unlock request") d, err = SetupExistingInstancePrivateMode() if err != nil { logging.L.Panic().Err(err). @@ -19,6 +27,7 @@ func StartupWithPrivateMode() (d *daemon.Daemon, err error) { } } else { // we need to setup a whole new instance and wait for Setup rest api call + logging.L.Info().Msg("Waiting for setup-instance request") d, err = SetupNewInstancePrivateMode() if err != nil { logging.L.Panic().Err(err). @@ -26,6 +35,29 @@ func StartupWithPrivateMode() (d *daemon.Daemon, err error) { } } + // Setup BlindBit Nostr Wallet Connect + nwcServer := nwcserver.NewNwcServer(d) + + var controller *nwc.Nip47Controller + logging.L.Info().Msg("attempting to load NWC apps from disk") + controller, err = d.DBWriter.TryLoadingControllerFromDisk(context.Background(), config.PathDbNWC) + if err != nil { + logging.L.Panic().Err(err).Msg("failed to create new controller") + } + logging.L.Trace().Any("apps", controller.Apps()).Msg("controller data") + + controller.RegisterHandler(nwc.GET_INFO_METHOD, nwcServer.GetInfoHandler()) + controller.RegisterHandler(nwc.GET_BALANCE_METHOD, nwcServer.GetBalanceHandler()) + controller.RegisterHandler(nwc.LIST_UTXOS_METHOD, nwcServer.ListUtxosHandler()) + + err = controller.ConnectRelay() + if err != nil { + logging.L.Panic().Err(err).Msg("failed to connect to relay") + } + go controller.StartListening() + + *nip47Controller = *controller + return d, nil } @@ -61,6 +93,8 @@ func SetupNewInstancePrivateMode() (d *daemon.Daemon, err error) { SecretKeyScan: setup.ScanSecretKey, PubKeySpend: setup.SpendPubKey, BirthHeight: setup.BirthHeight, + Labels: make(wallet.LabelMap), + UTXOMapping: make(wallet.UTXOMapping), } d.SetDbWriter(&database.DBWriter{Password: setup.Password}) diff --git a/pkg/database/db.go b/pkg/database/db.go index 40e0466..ce4efe0 100644 --- a/pkg/database/db.go +++ b/pkg/database/db.go @@ -5,6 +5,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/sha256" "errors" "fmt" "io" @@ -14,6 +15,7 @@ import ( "github.com/setavenger/blindbit-scan/internal/config" "github.com/setavenger/blindbit-scan/pkg/logging" "github.com/setavenger/blindbit-scan/pkg/networking/nwc" + "github.com/setavenger/blindbit-scan/pkg/types" "github.com/setavenger/blindbit-scan/pkg/wallet" ) @@ -71,8 +73,11 @@ func (w *DBWriter) EncryptData(data []byte) ([]byte, error) { // AES GCM encryption function func EncryptData(data []byte, password string) ([]byte, error) { - // Create a new AES cipher block using the password - block, err := aes.NewCipher([]byte(password)) + // Hash the password to get a 32-byte key + key := sha256.Sum256([]byte(password)) + + // Create a new AES cipher block using the hashed key + block, err := aes.NewCipher(key[:]) if err != nil { return nil, err } @@ -95,8 +100,11 @@ func EncryptData(data []byte, password string) ([]byte, error) { } func DecryptData(data []byte, password string) ([]byte, error) { - // Create a new AES cipher block using the password - block, err := aes.NewCipher([]byte(password)) + // Hash the password to get a 32-byte key + key := sha256.Sum256([]byte(password)) + + // Create a new AES cipher block using the hashed key + block, err := aes.NewCipher(key[:]) if err != nil { return nil, err } @@ -203,3 +211,13 @@ func (w *DBWriter) TryLoadingControllerFromDisk( return nwc.NewNip47Controller(ctx), nil } + +func (w *DBWriter) SaveAuthCredentials(creds *types.AuthCredentials) error { + return w.WriteToDB(config.PathDbAuth, creds) +} + +func (w *DBWriter) LoadAuthCredentials() (*types.AuthCredentials, error) { + var creds types.AuthCredentials + err := w.ReadFromDB(config.PathDbAuth, &creds) + return &creds, err +} diff --git a/pkg/types/auth.go b/pkg/types/auth.go new file mode 100644 index 0000000..b3836d0 --- /dev/null +++ b/pkg/types/auth.go @@ -0,0 +1,16 @@ +package types + +import "encoding/json" + +type AuthCredentials struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func (a *AuthCredentials) Serialise() ([]byte, error) { + return json.Marshal(a) +} + +func (a *AuthCredentials) DeSerialise(data []byte) error { + return json.Unmarshal(data, a) +} From ef08e53f3e79053dd99daa7a7f1ec74b2f098c62 Mon Sep 17 00:00:00 2001 From: setavenger Date: Fri, 25 Apr 2025 22:54:21 +0200 Subject: [PATCH 4/8] fixed bad address computation --- internal/startup/private.go | 16 ++++++++++------ internal/startup/simple.go | 8 ++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/internal/startup/private.go b/internal/startup/private.go index 75191e2..19f312e 100644 --- a/internal/startup/private.go +++ b/internal/startup/private.go @@ -89,14 +89,18 @@ func SetupNewInstancePrivateMode() (d *daemon.Daemon, err error) { Msg("startup failed, could only produce daemon hull") } - d.Wallet = &wallet.Wallet{ - SecretKeyScan: setup.ScanSecretKey, - PubKeySpend: setup.SpendPubKey, - BirthHeight: setup.BirthHeight, - Labels: make(wallet.LabelMap), - UTXOMapping: make(wallet.UTXOMapping), + if setup.LabelCount == 0 { + setup.LabelCount = 5 } + w, err := wallet.SetupWallet(setup.BirthHeight, setup.LabelCount, setup.ScanSecretKey, setup.SpendPubKey) + if err != nil { + logging.L.Err(err). + Msg("startup failed, could not setup wallet") + return nil, err + } + d.Wallet = w + d.SetDbWriter(&database.DBWriter{Password: setup.Password}) return d, nil diff --git a/internal/startup/simple.go b/internal/startup/simple.go index 5ea040b..4ea391d 100644 --- a/internal/startup/simple.go +++ b/internal/startup/simple.go @@ -35,10 +35,10 @@ func SetupNewInstanceSimpleMode() (d *daemon.Daemon, err error) { d.SetDbWriter(&database.DBWriter{Password: ""}) if config.ScanSecretKey != [32]byte{} && config.SpendPubKey != [33]byte{} { - w := &wallet.Wallet{ - SecretKeyScan: config.ScanSecretKey, - PubKeySpend: config.SpendPubKey, - BirthHeight: config.BirthHeight, + w, err := wallet.SetupWallet(config.BirthHeight, config.LabelCount, config.ScanSecretKey, config.SpendPubKey) + if err != nil { + logging.L.Panic().Err(err). + Msg("startup failed, could not setup wallet") } d.Wallet = w } From 7788fa6a0c7c9a1d6f4bcc650341893e60762be8 Mon Sep 17 00:00:00 2001 From: setavenger Date: Sun, 27 Apr 2025 14:31:03 +0200 Subject: [PATCH 5/8] should fix nil pointer errors --- pkg/networking/nwc/nip47_controller.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/networking/nwc/nip47_controller.go b/pkg/networking/nwc/nip47_controller.go index e7adedb..590c395 100644 --- a/pkg/networking/nwc/nip47_controller.go +++ b/pkg/networking/nwc/nip47_controller.go @@ -167,6 +167,9 @@ func (c *Nip47Controller) StartListening() { for { select { case ev := <-sub.Events: + if ev == nil { + continue + } logging.L.Info().Str("event-id", ev.ID).Msg("received event") go c.processEvent(ev) case <-c.ctx.Done(): From 6f52ac56d97f6e66f702121e02e6c1e80deb8d4e Mon Sep 17 00:00:00 2001 From: setavenger Date: Sun, 27 Apr 2025 15:31:10 +0200 Subject: [PATCH 6/8] increased basic auth password length in private mode --- internal/config/auth.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/internal/config/auth.go b/internal/config/auth.go index 19b8965..bf7e6e2 100644 --- a/internal/config/auth.go +++ b/internal/config/auth.go @@ -6,7 +6,6 @@ import ( "github.com/btcsuite/btcd/btcutil/base58" petname "github.com/dustinkirkland/golang-petname" - "github.com/setavenger/blindbit-scan/pkg/logging" "github.com/setavenger/blindbit-scan/pkg/types" ) @@ -16,11 +15,10 @@ var ( func GenerateAuthCredentials() *types.AuthCredentials { username := petname.Generate(2, "-") - randomBytes := make([]byte, 8) - if _, err := rand.Read(randomBytes); err != nil { - logging.L.Panic().Err(err).Msg("failed to generate random bytes") - return nil - } + randomBytes := make([]byte, 16) + + rand.Read(randomBytes) // never returns an error + password := base58.Encode(randomBytes) return &types.AuthCredentials{ From 41a10f4844e26814784caabe27347e1ef73156ca Mon Sep 17 00:00:00 2001 From: setavenger Date: Mon, 28 Apr 2025 08:42:21 +0200 Subject: [PATCH 7/8] removed dependencies on blindbitd --- go.mod | 6 ++---- go.sum | 4 ---- internal/config/paths.go | 2 +- internal/server/server.go | 2 +- pkg/networking/blindbit.go | 2 +- pkg/utils/bytes.go | 8 ++++++++ pkg/utils/path.go | 35 +++++++++++++++++++++++++++++++++++ 7 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 pkg/utils/bytes.go create mode 100644 pkg/utils/path.go diff --git a/go.mod b/go.mod index 80a1e0c..5e0ae51 100644 --- a/go.mod +++ b/go.mod @@ -8,16 +8,14 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/btcsuite/goleveldb v1.0.0 + github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 github.com/gin-contrib/cors v1.7.2 github.com/gin-gonic/gin v1.10.0 - github.com/google/uuid v1.6.0 github.com/nbd-wtf/go-nostr v0.50.0 github.com/rs/zerolog v1.33.0 - github.com/setavenger/blindbitd v0.0.0-20240602183715-c4e971bba3e4 github.com/setavenger/go-bip352 v0.1.7 github.com/setavenger/go-electrum v1.1.1 github.com/spf13/viper v1.19.0 - golang.org/x/text v0.24.0 ) require ( @@ -30,7 +28,6 @@ require ( github.com/coder/websocket v1.8.12 // indirect github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect - github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -72,6 +69,7 @@ require ( golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/net v0.37.0 // indirect golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.24.0 // indirect google.golang.org/protobuf v1.36.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index c5547b7..bdde8ad 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -164,8 +162,6 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/setavenger/blindbitd v0.0.0-20240602183715-c4e971bba3e4 h1:mKbdSQiWncNMS2AExIYQXvBjxziNMeo2oE1J0YCd0qs= -github.com/setavenger/blindbitd v0.0.0-20240602183715-c4e971bba3e4/go.mod h1:+L9bcVMbECVSqYjj/LhBKWNM4uVNRayN86EcVCrnFBI= github.com/setavenger/go-bip352 v0.1.7 h1:XckUqK+MadzOnfersbjXZuR9kLy40mW+BJGbWzYAt0A= github.com/setavenger/go-bip352 v0.1.7/go.mod h1:ajjkB64QrjbF0+MEUjeeBlBxDaJk7VmYUN8XbOK+EKo= github.com/setavenger/go-electrum v1.1.1 h1:2rowPjZE9BGLBHHenVwq6JWP0RKlqY2A5Bd6omprPjk= diff --git a/internal/config/paths.go b/internal/config/paths.go index 90a3877..832111e 100644 --- a/internal/config/paths.go +++ b/internal/config/paths.go @@ -1,7 +1,7 @@ package config // outsource to a common package as this is used across several blindbit programs -import "github.com/setavenger/blindbitd/src/utils" +import "github.com/setavenger/blindbit-scan/pkg/utils" var ( DirectoryPath = "~/.blindbit-scan" diff --git a/internal/server/server.go b/internal/server/server.go index e3410e5..b30d5e8 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -9,7 +9,7 @@ import ( "github.com/setavenger/blindbit-scan/internal/daemon" "github.com/setavenger/blindbit-scan/pkg/logging" "github.com/setavenger/blindbit-scan/pkg/networking/nwc" - "github.com/setavenger/blindbitd/src/utils" + "github.com/setavenger/blindbit-scan/pkg/utils" ) func StartNewServer( diff --git a/pkg/networking/blindbit.go b/pkg/networking/blindbit.go index 043df21..e698a1a 100644 --- a/pkg/networking/blindbit.go +++ b/pkg/networking/blindbit.go @@ -9,7 +9,7 @@ import ( "net/http" "github.com/setavenger/blindbit-scan/pkg/logging" - "github.com/setavenger/blindbitd/src/utils" + "github.com/setavenger/blindbit-scan/pkg/utils" "github.com/setavenger/go-bip352" ) diff --git a/pkg/utils/bytes.go b/pkg/utils/bytes.go new file mode 100644 index 0000000..3b85ad6 --- /dev/null +++ b/pkg/utils/bytes.go @@ -0,0 +1,8 @@ +package utils + +// ConvertToFixedLength34 converts a byte slice to a fixed length array of 34 bytes +func ConvertToFixedLength34(data []byte) [34]byte { + var result [34]byte + copy(result[:], data) + return result +} diff --git a/pkg/utils/path.go b/pkg/utils/path.go new file mode 100644 index 0000000..3ceb7f5 --- /dev/null +++ b/pkg/utils/path.go @@ -0,0 +1,35 @@ +package utils + +import ( + "os" + "path/filepath" + "strings" + + "github.com/setavenger/blindbit-scan/pkg/logging" +) + +// ResolvePath resolves a path, expanding ~ to the user's home directory +func ResolvePath(path string) string { + if strings.HasPrefix(path, "~") { + home, err := os.UserHomeDir() + if err != nil { + logging.L.Panic().Err(err).Msg("failed to get user home directory") + } + path = filepath.Join(home, path[1:]) + } + return path +} + +// CheckIfFileExists checks if a file exists +func CheckIfFileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +// TryCreateDirectoryPanic creates a directory and panics if it fails +func TryCreateDirectoryPanic(path string) { + err := os.MkdirAll(path, 0755) + if err != nil { + logging.L.Panic().Err(err).Msg("failed to create directory") + } +} From eb2176dd3b8229580313e4505aabd73ab8515843 Mon Sep 17 00:00:00 2001 From: setavenger Date: Thu, 1 May 2025 11:56:17 +0200 Subject: [PATCH 8/8] cleanup --- internal/config/load.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/config/load.go b/internal/config/load.go index d1d0060..9800aad 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -3,7 +3,6 @@ package config import ( "encoding/hex" "log" - "log/slog" "strings" "time" @@ -27,8 +26,7 @@ func LoadConfigs(pathToConfig string) (err error) { // Handle errors reading the config file if err = viper.ReadInConfig(); err != nil { - logging.L.Err(err).Msg("Error reading config file") - return + logging.L.Warn().Err(err).Msg("Error reading config file") } // map ENV var names @@ -99,8 +97,8 @@ func LoadConfigs(pathToConfig string) (err error) { AuthPass = viper.GetString("auth.pass") if (AuthUser == "" || AuthPass == "") && !PrivateMode { - err := errors.New("config is missing auth settings") - slog.Error(err.Error()) + err = errors.New("config is missing auth settings") + logging.L.Err(err).Msg("") return err }