diff --git a/README.md b/README.md index 1e2b834..6cb2333 100644 --- a/README.md +++ b/README.md @@ -268,10 +268,6 @@ Selects an unlocker as the current default for operations. Imports a secret from a file and stores it in the current vault under the given name. -#### `secret vault import [vault-name]` - -Imports a mnemonic phrase into the specified vault (defaults to "default"). - ### Encryption Operations #### `secret encrypt [--input=file] [--output=file]` @@ -377,9 +373,7 @@ line or in a CI job, they end up in shell history and CI logs. `secret` unsets each one as soon as it has read it, so that the programs it runs itself, such as `gpg`, do not inherit it, but that erases nothing: the environment the process started with, and its memory, still hold the value. The interactive prompt, -which every command except `secret vault import` offers when the variable is not -set, is the safer default; `secret vault import` has no prompt and needs both -variables. +which every command offers when the variable is not set, is the safer default. The prompt asks on the terminal the command runs in, not on stdin, so it also asks when stdin is a pipe, as in `echo value | secret add name --force` or diff --git a/internal/cli/create_vault_test.go b/internal/cli/create_vault_test.go index 403fd31..23c3b51 100644 --- a/internal/cli/create_vault_test.go +++ b/internal/cli/create_vault_test.go @@ -141,6 +141,30 @@ func TestVaultCreationLeavesNoSecretInEnvironment(t *testing.T) { run("vault", "create", "work") } +// TestNoVaultImportCommand is a regression test for +// https://git.eeqj.de/sneak/secret/issues/139: `secret vault import` +// refused every vault that `secret vault create` makes, because each has a +// long-term key. The command is gone, so cobra answers it with the help of +// `secret vault`, as it does any subcommand of `secret vault` it does not +// know. +func TestNoVaultImportCommand(t *testing.T) { + t.Setenv(secret.EnvStateDir, t.TempDir()) + + run := func(args ...string) string { + t.Setenv(secret.EnvMnemonic, testMnemonic) + t.Setenv(secret.EnvUnlockPassphrase, testPassphrase) + + output, err := cli.ExecuteCommandInProcess(args, "", nil) + require.NoError(t, err, "%v", args) + + return output + } + + run("vault", "create", "work") + + require.Equal(t, run("vault"), run("vault", "import", "work")) +} + // TestStopAtPassphrasePromptLeavesNothing is a regression test for the // review of https://git.eeqj.de/sneak/secret/pulls/82: `secret init` or // `secret vault create` stopped at the passphrase prompt left a vault with diff --git a/internal/cli/generate.go b/internal/cli/generate.go index 631e6bd..f902358 100644 --- a/internal/cli/generate.go +++ b/internal/cli/generate.go @@ -43,7 +43,7 @@ func newGenerateMnemonicCmd() *cobra.Command { Short: "Generate a random BIP39 mnemonic phrase", Long: `Generate a cryptographically secure random BIP39 ` + `mnemonic phrase that can be used with 'secret init' ` + - `or 'secret import'.`, + `or 'secret vault create'.`, RunE: func(cmd *cobra.Command, _ []string) error { cli, err := NewCLIInstance() if err != nil { @@ -115,8 +115,10 @@ func (cli *Instance) GenerateMnemonic(cmd *cobra.Command) error { " • If you lose this phrase, your secrets cannot be recovered") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Use this mnemonic with:") - fmt.Fprintln(os.Stderr, " secret init (to initialize a new secret manager)") - fmt.Fprintln(os.Stderr, " secret import (to import into an existing vault)") + fmt.Fprintln(os.Stderr, + " secret init (to initialize a new secret manager)") + fmt.Fprintln(os.Stderr, + " secret vault create (to create another vault)") return nil } diff --git a/internal/cli/input_errors_test.go b/internal/cli/input_errors_test.go index fae6d1b..54280b3 100644 --- a/internal/cli/input_errors_test.go +++ b/internal/cli/input_errors_test.go @@ -7,12 +7,10 @@ import ( "github.com/awnumar/memguard" "github.com/spf13/afero" "github.com/stretchr/testify/require" - "sneak.berlin/go/secret/internal/vault" ) // TestInvalidMnemonicError checks that every command that takes a mnemonic -// returns errInvalidMnemonicPhrase for one that is not valid BIP39. The vault -// "other" has no long-term key, as vault import needs. +// returns errInvalidMnemonicPhrase for one that is not valid BIP39. func TestInvalidMnemonicError(t *testing.T) { t.Parallel() @@ -24,20 +22,13 @@ func TestInvalidMnemonicError(t *testing.T) { {"secret vault create work", func(c *Instance) error { return c.CreateVault(c.cmd, "work") }}, - {"secret vault import other", func(c *Instance) error { - return c.VaultImport(c.cmd, "other") - }}, } for _, tt := range tests { t.Run(tt.command, func(t *testing.T) { t.Parallel() - fs := afero.NewMemMapFs() - _, err := vault.CreateVault(fs, listTestStateDir, "other", nil, nil) - require.NoError(t, err) - - instance, _ := newTestInstance(fs) + instance, _ := newTestInstance(afero.NewMemMapFs()) instance.Mnemonic = memguard.NewBufferFromBytes([]byte("not a mnemonic")) t.Cleanup(instance.Mnemonic.Destroy) diff --git a/internal/cli/integration_test.go b/internal/cli/integration_test.go index 3507aa6..1d5a3d9 100644 --- a/internal/cli/integration_test.go +++ b/internal/cli/integration_test.go @@ -140,16 +140,6 @@ func TestSecretManagerIntegration(t *testing.T) { // - currentvault symlink updated to point to work test03CreateVault(t, tempDir, runSecret) - // Test 4: Import mnemonic into work vault - // Command: secret vault import work - // Purpose: Import mnemonic with passphrase unlocker - // Expected filesystem: - // - work vault has pub.age file - // - work vault has unlockers.d/passphrase directory - // - Unlocker metadata and encrypted keys present - // NOTE: Skipped because vault creation now includes mnemonic import - // test04ImportMnemonic(t, tempDir, testMnemonic, testPassphrase, runSecretWithEnv) - // Test 5: Add secrets with versioning // Command: echo "password123" | secret add database/password // Purpose: Add a secret and verify versioned storage @@ -535,65 +525,6 @@ func test03CreateVault(t *testing.T, tempDir string, runSecret func(...string) ( assert.Contains(t, output, "work", "should list work vault") } -//nolint:unused // TODO: re-enable when vault import is implemented -func test04ImportMnemonic(t *testing.T, tempDir, testMnemonic, testPassphrase string, runSecretWithEnv func(map[string]string, ...string) (string, error)) { - t.Helper() - - // Import mnemonic into work vault - output, err := runSecretWithEnv(map[string]string{ - secret.EnvMnemonic: testMnemonic, - secret.EnvUnlockPassphrase: testPassphrase, - }, "vault", "import", "work") - - require.NoError(t, err, "vault import should succeed") - assert.Contains(t, output, "Successfully imported mnemonic into vault 'work'", "should confirm import") - - // Verify work vault now has long-term key - workVaultDir := filepath.Join(tempDir, "vaults.d", "work") - pubKeyFile := filepath.Join(workVaultDir, "pub.age") - verifyFileExists(t, pubKeyFile) - - // Verify passphrase unlocker was created - currentUnlocker := readFile(t, filepath.Join(workVaultDir, "current-unlocker")) - passphraseUnlockerDir := filepath.Join(workVaultDir, "unlockers.d", string(currentUnlocker)) - verifyFileExists(t, passphraseUnlockerDir) - - // Check unlocker files - unlockerMetadata := filepath.Join(passphraseUnlockerDir, "unlocker-metadata.json") - verifyFileExists(t, unlockerMetadata) - - encryptedLTPrivKey := filepath.Join(passphraseUnlockerDir, "priv.age") - verifyFileExists(t, encryptedLTPrivKey) - - encryptedLTPubKey := filepath.Join(passphraseUnlockerDir, "pub.age") - verifyFileExists(t, encryptedLTPubKey) - - longtermKeyFile := filepath.Join(passphraseUnlockerDir, "longterm.age") - verifyFileExists(t, longtermKeyFile) - - // Verify vault metadata was created with derivation index - vaultMetadata := filepath.Join(workVaultDir, "vault-metadata.json") - verifyFileExists(t, vaultMetadata) - - metadataBytes := readFile(t, vaultMetadata) - - var metadata map[string]any - - err = json.Unmarshal(metadataBytes, &metadata) - require.NoError(t, err, "vault metadata should be valid JSON") - - // Work vault should have a different derivation index than default (0) - derivIndex, ok := metadata["derivationIndex"].(float64) - require.True(t, ok, "derivationIndex should be a number") - assert.NotEqual(t, float64(0), derivIndex, "work vault should have non-zero derivation index") - - // Verify public key hash is stored - assert.Contains(t, metadata, "publicKeyHash", "should contain public key hash") - pubKeyHash, ok := metadata["publicKeyHash"].(string) - require.True(t, ok, "publicKeyHash should be a string") - assert.NotEmpty(t, pubKeyHash, "public key hash should not be empty") -} - func test05AddSecret(t *testing.T, tempDir, testMnemonic string, runSecret func(...string) (string, error), runSecretWithEnv func(map[string]string, ...string) (string, error), runSecretWithStdin func(string, map[string]string, ...string) (string, error)) { t.Helper() @@ -1939,14 +1870,6 @@ func test23ErrorHandling(t *testing.T, tempDir, secretPath, testMnemonic string, _, err = runSecret("vault", "select", "nonexistent") require.ErrorIs(t, err, vault.ErrVaultNotFound, "select non-existent vault should fail") - // Import to non-existent vault with test passphrase - testPassphrase := "test-passphrase-123" // Define testPassphrase locally - _, err = runSecretWithEnv(map[string]string{ - secret.EnvMnemonic: testMnemonic, - secret.EnvUnlockPassphrase: testPassphrase, - }, "vault", "import", "nonexistent") - require.ErrorIs(t, err, vault.ErrVaultNotFound, "import to non-existent vault should fail") - // Get specific version that doesn't exist _, err = runSecretWithEnv(map[string]string{ secret.EnvMnemonic: testMnemonic, diff --git a/internal/cli/lock_test.go b/internal/cli/lock_test.go index caa2ee3..b199f0d 100644 --- a/internal/cli/lock_test.go +++ b/internal/cli/lock_test.go @@ -282,11 +282,10 @@ func stateDirModTimes(t *testing.T, fs afero.Fs) map[string]int64 { // setupEveryCommand makes what each command in // TestChangingCommandsWaitForLock needs: the current vault "work" with two -// versions of "test/secret", the vault "other" without a long-term key, for -// vault import, and the file testInput. There is no vault "default", which -// init creates. If withUnlocker is set, it also gives "work" a passphrase -// unlocker, which is slow. It returns the older version and the unlocker's -// ID. +// versions of "test/secret", the vault "other", and the file testInput. +// There is no vault "default", which init creates. If withUnlocker is set, +// it also gives "work" a passphrase unlocker, which is slow. It returns the +// older version and the unlocker's ID. func setupEveryCommand( t *testing.T, fs afero.Fs, withUnlocker bool, ) (string, string) { @@ -294,13 +293,9 @@ func setupEveryCommand( mnemonic := testMnemonicBuffer(t) - other, err := vault.CreateVault(fs, testStateDir, "other", mnemonic, nil) + _, err := vault.CreateVault(fs, testStateDir, "other", mnemonic, nil) require.NoError(t, err) - otherDir, err := other.GetDirectory() - require.NoError(t, err) - require.NoError(t, fs.Remove(filepath.Join(otherDir, "pub.age"))) - vlt, err := vault.CreateVault(fs, testStateDir, "work", mnemonic, nil) require.NoError(t, err) @@ -455,9 +450,6 @@ func TestChangingCommandsWaitForLock(t *testing.T) { {"vault select", false, func(cli *Instance, _, _ string) error { return cli.SelectVault(cli.cmd, "other") }}, - {"vault import", false, func(cli *Instance, _, _ string) error { - return cli.VaultImport(cli.cmd, "other") - }}, {"vault rm --force", false, func(cli *Instance, _, _ string) error { return cli.RemoveVault(cli.cmd, "other", true) }}, diff --git a/internal/cli/path_traversal_test.go b/internal/cli/path_traversal_test.go index 7c85250..5046c2c 100644 --- a/internal/cli/path_traversal_test.go +++ b/internal/cli/path_traversal_test.go @@ -306,8 +306,7 @@ func TestInvalidVersionLeavesVaultsUnchanged(t *testing.T) { // TestInvalidVaultNameLeavesStateUnchanged is a regression test for // https://git.eeqj.de/sneak/secret/issues/68, where -// `secret vault import ..` wrote a long-term key and an unlocker into the -// state directory itself, and `secret vault select ..` made it the current +// `secret vault select ..` made the state directory itself the current // vault. Each command that takes a vault name must reject an invalid one // before building a path from it. The instance is given the mnemonic and // the passphrase, and moves and removals use --force, so that only the name @@ -331,9 +330,6 @@ func TestInvalidVaultNameLeavesStateUnchanged(t *testing.T) { {"vault create %q", func(c *cli.Instance, name string) error { return c.CreateVault(cmd, name) }}, - {"vault import %q", func(c *cli.Instance, name string) error { - return c.VaultImport(cmd, name) - }}, {"vault select %q", func(c *cli.Instance, name string) error { return c.SelectVault(cmd, name) }}, diff --git a/internal/cli/unreadable_dir_test.go b/internal/cli/unreadable_dir_test.go index 96da582..fef88c2 100644 --- a/internal/cli/unreadable_dir_test.go +++ b/internal/cli/unreadable_dir_test.go @@ -371,19 +371,3 @@ func TestRemoveSecretAbortsWhenVersionsUnreadable(t *testing.T) { require.NoError(t, err) assert.True(t, exists, "the secret must not be removed") } - -// TestVaultImportAbortsWhenPubKeyUnreadable asserts that a mnemonic import -// stops when whether the vault already has a long-term key cannot be -// determined. -func TestVaultImportAbortsWhenPubKeyUnreadable(t *testing.T) { - t.Parallel() - - base := newListTestVault(t, 1) - instance, cmd := newTestInstance(&statFailFs{ - Fs: base, path: filepath.Join(testVaultDir(listTestVaultName), "pub.age"), - }) - - err := instance.importMnemonic(cmd, listTestVaultName) - - require.ErrorIs(t, err, errStatFailed) -} diff --git a/internal/cli/vault.go b/internal/cli/vault.go index d88e051..ecf874c 100644 --- a/internal/cli/vault.go +++ b/internal/cli/vault.go @@ -8,7 +8,6 @@ import ( "path/filepath" "slices" "strings" - "time" "github.com/awnumar/memguard" "github.com/spf13/afero" @@ -16,19 +15,12 @@ import ( "sneak.berlin/go/secret/internal/bip39" "sneak.berlin/go/secret/internal/secret" "sneak.berlin/go/secret/internal/vault" - "sneak.berlin/go/secret/pkg/agehd" ) // Sentinel errors for vault operations var ( errMnemonicEmpty = errors.New("mnemonic cannot be empty") errInvalidMnemonicPhrase = errors.New("invalid BIP39 mnemonic phrase") - errVaultHasLongTermKey = errors.New( - "already has a long-term key configured") - errMnemonicEnvNotSet = errors.New( - "SB_SECRET_MNEMONIC environment variable not set") - errPassphraseEnvNotSet = errors.New( - "SB_UNLOCK_PASSPHRASE environment variable not set") errCannotRemoveLastVault = errors.New("cannot remove the last vault") ) @@ -42,7 +34,6 @@ func newVaultCmd() *cobra.Command { cmd.AddCommand(newVaultListCmd()) cmd.AddCommand(newVaultCreateCmd()) cmd.AddCommand(newVaultSelectCmd()) - cmd.AddCommand(newVaultImportCmd()) cmd.AddCommand(newVaultRemoveCmd()) return cmd @@ -111,38 +102,6 @@ func newVaultSelectCmd() *cobra.Command { } } -func newVaultImportCmd() *cobra.Command { - cli, err := NewCLIInstance() - if err != nil { - log.Fatalf("failed to initialize CLI: %v", err) - } - - return &cobra.Command{ - Use: "import ", - Short: "Import a mnemonic into a vault", - Long: `Import a BIP39 mnemonic phrase into the specified vault ` + - `(default if not specified).`, - Args: cobra.MaximumNArgs(1), - ValidArgsFunction: getVaultNamesCompletionFunc(cli.fs, cli.stateDir), - RunE: func(cmd *cobra.Command, args []string) error { - vaultName := "default" - if len(args) > 0 { - vaultName = args[0] - } - - cli, err := NewCLIInstance() - if err != nil { - return fmt.Errorf("failed to initialize CLI: %w", err) - } - - destroySecrets := cli.readSecretEnv() - defer destroySecrets() - - return cli.VaultImport(cmd, vaultName) - }, - } -} - func newVaultRemoveCmd() *cobra.Command { cli, err := NewCLIInstance() if err != nil { @@ -336,196 +295,6 @@ func (cli *Instance) SelectVault(cmd *cobra.Command, name string) error { return nil } -// vaultImportPreflight verifies the vault exists without a long-term key -// and returns the vault directory, public key path, and validated mnemonic -func (cli *Instance) vaultImportPreflight( - vlt *vault.Vault, vaultName string, -) (string, string, string, error) { - // Check if vault exists - vaultDir, err := vlt.GetDirectory() - if err != nil { - return "", "", "", err - } - - exists, err := afero.DirExists(cli.fs, vaultDir) - if err != nil { - return "", "", "", fmt.Errorf("failed to check if vault exists: %w", err) - } - - if !exists { - return "", "", "", fmt.Errorf("vault '%s' %w", - vaultName, vault.ErrVaultNotFound) - } - - // Check if vault already has a public key - pubKeyPath := vaultDir + "/pub.age" - - exists, err = afero.Exists(cli.fs, pubKeyPath) - if err != nil { - return "", "", "", fmt.Errorf("failed to check %s: %w", pubKeyPath, err) - } - - if exists { - return "", "", "", fmt.Errorf("vault '%s' %w", - vaultName, errVaultHasLongTermKey) - } - - // Get mnemonic from environment - if cli.Mnemonic == nil { - return "", "", "", errMnemonicEnvNotSet - } - - mnemonic := cli.Mnemonic.String() - - // Validate the mnemonic - mnemonicWords := strings.Fields(mnemonic) - secret.Debug("Validating BIP39 mnemonic", "word_count", len(mnemonicWords)) - - if !bip39.IsMnemonicValid(mnemonic) { - return "", "", "", errInvalidMnemonicPhrase - } - - return vaultDir, pubKeyPath, mnemonic, nil -} - -// updateVaultImportMetadata stores the derivation info in vault metadata -func updateVaultImportMetadata( - fs afero.Fs, vaultDir string, derivationIndex uint32, - publicKeyHash, familyHash string, -) error { - // Load existing metadata - existingMetadata, err := vault.LoadVaultMetadata(fs, vaultDir) - if err != nil { - // If metadata doesn't exist, create new - existingMetadata = &vault.Metadata{ - CreatedAt: time.Now(), - } - } - - // Update metadata with new derivation info - existingMetadata.DerivationIndex = derivationIndex - existingMetadata.PublicKeyHash = publicKeyHash - existingMetadata.MnemonicFamilyHash = familyHash - - err = vault.SaveVaultMetadata(fs, vaultDir, existingMetadata) - if err != nil { - secret.Debug("Failed to save vault metadata", "error", err) - - return fmt.Errorf("failed to save vault metadata: %w", err) - } - - secret.Debug("Saved vault metadata with derivation index and public key hash") - - return nil -} - -// VaultImport imports a mnemonic into a specific vault, holding the state -// directory lock while importMnemonic runs -func (cli *Instance) VaultImport(cmd *cobra.Command, vaultName string) error { - err := vault.ValidateVaultName(vaultName) - if err != nil { - return err - } - - release, err := vault.LockStateDir(cli.fs, cli.stateDir) - if err != nil { - return err - } - defer release() - - return cli.importMnemonic(cmd, vaultName) -} - -// importMnemonic gives the vault a long-term key derived from the mnemonic -// and a passphrase unlocker -func (cli *Instance) importMnemonic(cmd *cobra.Command, vaultName string) error { - secret.Debug("Importing mnemonic into vault", - "vault_name", vaultName, "state_dir", cli.stateDir) - - // Get the specific vault by name - vlt := vault.NewVault(cli.fs, cli.stateDir, vaultName) - - vaultDir, pubKeyPath, mnemonic, err := cli.vaultImportPreflight(vlt, vaultName) - if err != nil { - return err - } - - // Get the next available derivation index for this mnemonic - derivationIndex, err := vault.GetNextDerivationIndex(cli.fs, cli.stateDir, mnemonic) - if err != nil { - secret.Debug("Failed to get next derivation index", "error", err) - - return fmt.Errorf("failed to get next derivation index: %w", err) - } - - secret.Debug("Using derivation index", "index", derivationIndex) - - // Derive long-term key from mnemonic with the appropriate index - secret.Debug("Deriving long-term key from mnemonic", "index", derivationIndex) - - ltIdentity, err := agehd.DeriveIdentity(mnemonic, derivationIndex) - if err != nil { - return fmt.Errorf("failed to derive long-term key: %w", err) - } - - // Store long-term public key in vault - ltPublicKey := ltIdentity.Recipient().String() - secret.Debug("Storing long-term public key", - "pubkey", ltPublicKey, "vault_dir", vaultDir) - - err = secret.WriteFileAtomic(cli.fs, pubKeyPath, []byte(ltPublicKey)) - if err != nil { - return fmt.Errorf("failed to store long-term public key: %w", err) - } - - // Calculate public key hash from the actual derivation index being used - // This is used to verify that the derived key matches what was stored - publicKeyHash := vault.ComputeDoubleSHA256([]byte(ltIdentity.Recipient().String())) - - // Calculate family hash from index 0 (same for all vaults with this - // mnemonic). This is used to identify which vaults belong to the same - // mnemonic family. - identity0, err := agehd.DeriveIdentity(mnemonic, 0) - if err != nil { - return fmt.Errorf("failed to derive identity for index 0: %w", err) - } - - familyHash := vault.ComputeDoubleSHA256([]byte(identity0.Recipient().String())) - - err = updateVaultImportMetadata( - cli.fs, vaultDir, derivationIndex, publicKeyHash, familyHash) - if err != nil { - return err - } - - // Get passphrase from environment variable - passphraseBuffer := cli.UnlockPassphrase - if passphraseBuffer == nil { - return errPassphraseEnvNotSet - } - - secret.Debug("Using unlock passphrase from environment variable") - - // Unlock the vault with the derived long-term key - vlt.Unlock(ltIdentity) - - // Create passphrase-protected unlocker - secret.Debug("Creating passphrase-protected unlocker") - - passphraseUnlocker, err := vlt.CreatePassphraseUnlocker(passphraseBuffer) - if err != nil { - secret.Debug("Failed to create unlocker", "error", err) - - return fmt.Errorf("failed to create unlocker: %w", err) - } - - cmd.Printf("Successfully imported mnemonic into vault '%s'\n", vaultName) - cmd.Printf("Long-term public key: %s\n", ltPublicKey) - cmd.Printf("Unlocker ID: %s\n", passphraseUnlocker.GetID()) - - return nil -} - // countVaultSecrets returns the number of secrets in the vault directory func (cli *Instance) countVaultSecrets(vaultDir string) (int, error) { secretsDir := filepath.Join(vaultDir, "secrets.d") diff --git a/internal/vault/metadata_test.go b/internal/vault/metadata_test.go index f3d6206..0b05b21 100644 --- a/internal/vault/metadata_test.go +++ b/internal/vault/metadata_test.go @@ -296,91 +296,6 @@ func TestSampleHashCalculation(t *testing.T) { } } -func TestWorkflowMismatch(t *testing.T) { - t.Parallel() - - // Create a temporary directory for testing - tempDir := t.TempDir() - fs := afero.NewOsFs() - - // Test Case 1: Create vault WITH mnemonic (like init command) - _, err := vault.CreateVault(fs, tempDir, "default", testMnemonicBuffer(t), nil) - if err != nil { - t.Fatalf("Failed to create vault with mnemonic: %v", err) - } - - // Load metadata for vault1 - vault1Dir := filepath.Join(tempDir, "vaults.d", "default") - - metadata1, err := vault.LoadVaultMetadata(fs, vault1Dir) - if err != nil { - t.Fatalf("Failed to load vault1 metadata: %v", err) - } - - t.Logf("Vault1 (with mnemonic) - DerivationIndex: %d, PublicKeyHash: %s", - metadata1.DerivationIndex, metadata1.PublicKeyHash) - - // Test Case 2: Create vault WITHOUT mnemonic, then import (work vault) - _, err = vault.CreateVault(fs, tempDir, "work", nil, nil) - if err != nil { - t.Fatalf("Failed to create vault without mnemonic: %v", err) - } - - vault2Dir := filepath.Join(tempDir, "vaults.d", "work") - - // Simulate the vault import process: get the next available derivation - // index for this mnemonic - derivationIndex, err := vault.GetNextDerivationIndex(fs, tempDir, testMnemonic) - if err != nil { - t.Fatalf("Failed to get next derivation index: %v", err) - } - - t.Logf("Next derivation index for import: %d", derivationIndex) - - // Calculate public key hash from index 0 (same as in VaultImport) - identity0, err := agehd.DeriveIdentity(testMnemonic, 0) - if err != nil { - t.Fatalf("Failed to derive identity for index 0: %v", err) - } - - publicKeyHash := vault.ComputeDoubleSHA256( - []byte(identity0.Recipient().String())) - - // Load existing metadata and update it (same as in VaultImport) - existingMetadata, err := vault.LoadVaultMetadata(fs, vault2Dir) - if err != nil { - t.Fatalf("Failed to load existing metadata: %v", err) - } - - // Update metadata with new derivation info - existingMetadata.DerivationIndex = derivationIndex - existingMetadata.PublicKeyHash = publicKeyHash - - err = vault.SaveVaultMetadata(fs, vault2Dir, existingMetadata) - if err != nil { - t.Fatalf("Failed to save vault metadata: %v", err) - } - - // Load updated metadata for vault2 - metadata2, err := vault.LoadVaultMetadata(fs, vault2Dir) - if err != nil { - t.Fatalf("Failed to load vault2 metadata: %v", err) - } - - t.Logf("Vault2 (imported mnemonic) - DerivationIndex: %d, PublicKeyHash: %s", - metadata2.DerivationIndex, metadata2.PublicKeyHash) - - // Verify that both vaults have the same public key hash - if metadata1.PublicKeyHash != metadata2.PublicKeyHash { - t.Errorf("Public key hashes don't match!") - t.Logf("Vault1 hash: %s", metadata1.PublicKeyHash) - t.Logf("Vault2 hash: %s", metadata2.PublicKeyHash) - } else { - t.Logf("SUCCESS: Both vaults have the same public key hash: %s", - metadata1.PublicKeyHash) - } -} - func TestReverseEngineerHash(t *testing.T) { t.Parallel()