mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
* fix: remove legacy acceptance of empty unlock password check CheckUnlockPassword previously treated a missing or empty UnlockPasswordCheck value as a match — a legacy compatibility path from before the canary was always written. It now requires the stored value to be present and to equal the expected string. StartApp checks for the canary up front and, if it is missing, stops with a message asking the user to restore from a backup rather than continuing. A new IsUnlockPasswordCheckSet helper reports whether the value is present. keys.Init now returns the error from reading NostrSecretKey instead of ignoring it, so a read failure aborts instead of generating and saving a new key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: add operation context to unlock password check errors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
176 lines
5.4 KiB
Go
176 lines
5.4 KiB
Go
package keys
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/getAlby/hub/config"
|
|
"github.com/getAlby/hub/logger"
|
|
"github.com/getAlby/hub/tests/db"
|
|
|
|
"github.com/getAlby/go-nostr"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tyler-smith/go-bip32"
|
|
"github.com/tyler-smith/go-bip39"
|
|
)
|
|
|
|
func TestUseExistingMnemonic(t *testing.T) {
|
|
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
|
|
gormDb, err := db.NewDB(t)
|
|
require.NoError(t, err)
|
|
defer db.CloseDB(gormDb)
|
|
|
|
mnemonic := "thought turkey ask pottery head say catalog desk pledge elbow naive mimic"
|
|
unlockPassword := "123"
|
|
|
|
config, err := config.NewConfig(&config.AppConfig{}, gormDb)
|
|
require.NoError(t, err)
|
|
config.SetUpdate("Mnemonic", mnemonic, unlockPassword)
|
|
|
|
keys := NewKeys()
|
|
err = keys.Init(config, unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
mnemonicFromConfig, err := config.Get("Mnemonic", unlockPassword)
|
|
require.NoError(t, err)
|
|
require.Equal(t, mnemonic, mnemonicFromConfig)
|
|
|
|
// ensure backup key uses correct derivation path
|
|
derivedKeyFromKeys, err := keys.DeriveKey([]uint32{bip32.FirstHardenedChild})
|
|
require.NoError(t, err)
|
|
|
|
masterKey, err := bip32.NewMasterKey(bip39.NewSeed(mnemonic, ""))
|
|
assert.NoError(t, err)
|
|
|
|
appKey, err := masterKey.NewChildKey(bip32.FirstHardenedChild + 128029 /* 🐝 */)
|
|
assert.NoError(t, err)
|
|
|
|
encryptedChannelsBackupKey, err := appKey.NewChildKey(bip32.FirstHardenedChild)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, encryptedChannelsBackupKey.String(), derivedKeyFromKeys.String())
|
|
|
|
// get a wallet key for app ID 2, expect it is derived correctly
|
|
appWalletPrivateKey, err := keys.GetAppWalletKey(2)
|
|
require.NoError(t, err)
|
|
appWalletPubkey, err := nostr.GetPublicKey(appWalletPrivateKey)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "dd9e304d24f29f3481d5cf18a76c85ca3e95931aee3c997a27f267e975e72976", appWalletPubkey)
|
|
}
|
|
|
|
func TestGenerateNewMnemonic(t *testing.T) {
|
|
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
|
|
gormDb, err := db.NewDB(t)
|
|
require.NoError(t, err)
|
|
defer db.CloseDB(gormDb)
|
|
|
|
unlockPassword := "123"
|
|
|
|
config, err := config.NewConfig(&config.AppConfig{}, gormDb)
|
|
require.NoError(t, err)
|
|
|
|
keys := NewKeys()
|
|
err = keys.Init(config, unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
mnemonicFromConfig, err := config.Get("Mnemonic", unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
// expect a new 12-word mnemonic to be saved
|
|
assert.Equal(t, 12, len(strings.Split(mnemonicFromConfig, " ")))
|
|
|
|
// re-create keys, ensure same mnemonic is used
|
|
keys = NewKeys()
|
|
err = keys.Init(config, unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
mnemonicFromConfig2, err := config.Get("Mnemonic", unlockPassword)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, mnemonicFromConfig, mnemonicFromConfig2)
|
|
|
|
// check derivation
|
|
|
|
derivedKeyFromKeys, err := keys.DeriveKey([]uint32{bip32.FirstHardenedChild})
|
|
require.NoError(t, err)
|
|
|
|
masterKey, err := bip32.NewMasterKey(bip39.NewSeed(mnemonicFromConfig, ""))
|
|
assert.NoError(t, err)
|
|
|
|
appKey, err := masterKey.NewChildKey(bip32.FirstHardenedChild + 128029 /* 🐝 */)
|
|
assert.NoError(t, err)
|
|
|
|
encryptedChannelsBackupKey, err := appKey.NewChildKey(bip32.FirstHardenedChild)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, encryptedChannelsBackupKey.String(), derivedKeyFromKeys.String())
|
|
}
|
|
|
|
func TestInit_WrongPasswordDoesNotOverwriteNostrKey(t *testing.T) {
|
|
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
|
|
gormDb, err := db.NewDB(t)
|
|
require.NoError(t, err)
|
|
defer db.CloseDB(gormDb)
|
|
|
|
unlockPassword := "correct"
|
|
|
|
cfg, err := config.NewConfig(&config.AppConfig{}, gormDb)
|
|
require.NoError(t, err)
|
|
|
|
// initialise keys under the correct password, storing an encrypted NostrSecretKey
|
|
keys := NewKeys()
|
|
err = keys.Init(cfg, unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
originalSecret, err := cfg.Get("NostrSecretKey", unlockPassword)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, originalSecret)
|
|
|
|
// a wrong password must abort instead of mistaking the failed decrypt for
|
|
// "no key yet" and overwriting the stored key with a freshly generated one
|
|
keys2 := NewKeys()
|
|
err = keys2.Init(cfg, "wrong")
|
|
require.Error(t, err)
|
|
|
|
// the stored key, decrypted with the correct password, must be unchanged
|
|
secretAfter, err := cfg.Get("NostrSecretKey", unlockPassword)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, originalSecret, secretAfter)
|
|
}
|
|
|
|
func TestGenerateSwapMnemonic(t *testing.T) {
|
|
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
|
|
gormDb, err := db.NewDB(t)
|
|
require.NoError(t, err)
|
|
defer db.CloseDB(gormDb)
|
|
|
|
mnemonic := "thought turkey ask pottery head say catalog desk pledge elbow naive mimic"
|
|
unlockPassword := "123"
|
|
|
|
config, err := config.NewConfig(&config.AppConfig{}, gormDb)
|
|
require.NoError(t, err)
|
|
config.SetUpdate("Mnemonic", mnemonic, unlockPassword)
|
|
|
|
keys := NewKeys()
|
|
err = keys.Init(config, unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
mnemonicFromConfig, err := config.Get("Mnemonic", unlockPassword)
|
|
require.NoError(t, err)
|
|
require.Equal(t, mnemonic, mnemonicFromConfig)
|
|
|
|
seed := bip39.NewSeed(mnemonicFromConfig, "")
|
|
masterKey, err := bip32.NewMasterKey(seed)
|
|
require.NoError(t, err)
|
|
|
|
swapMnemonic, err := keys.GenerateSwapMnemonic(masterKey)
|
|
require.NoError(t, err)
|
|
|
|
// this matches https://iancoleman.io/bip39/ -> check "Show BIP85" and set BIP85 Index to 128260
|
|
expectedSwapMnemonic := "truth cargo pluck prefer mosquito symptom review kitchen exile fit corn vault"
|
|
assert.Equal(t, expectedSwapMnemonic, swapMnemonic)
|
|
}
|