feat: add derivation index to vault metadata for unique keys - Add VaultMetadata fields: DerivationIndex, LongTermKeyHash, MnemonicHash - Implement GetNextDerivationIndex() to track and increment indices for same mnemonics - Update init and import commands to use proper derivation indices - Add ComputeDoubleSHA256() for hash calculations - Save vault metadata on creation with all derivation information - Add comprehensive tests for metadata functionality. This ensures multiple vaults using the same mnemonic will derive different long-term keys by using incremented derivation indices. The mnemonic is double SHA256 hashed and stored to track which vaults share mnemonics. Fixes TODO item #5

This commit is contained in:
2025-05-29 16:23:29 -07:00
parent 1a1b11c5a3
commit 34d6870e6a
7 changed files with 378 additions and 18 deletions

View File

@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"strings"
"time"
"filippo.io/age"
"git.eeqj.de/sneak/secret/internal/secret"
@@ -74,14 +75,30 @@ func (cli *CLIInstance) Init(cmd *cobra.Command) error {
return fmt.Errorf("invalid BIP39 mnemonic phrase\nRun 'secret generate mnemonic' to create a valid mnemonic")
}
// Derive long-term keypair from mnemonic
secret.DebugWith("Deriving long-term key from mnemonic", slog.Int("index", 0))
ltIdentity, err := agehd.DeriveIdentity(mnemonicStr, 0)
// Calculate mnemonic hash for index tracking
mnemonicHash := vault.ComputeDoubleSHA256([]byte(mnemonicStr))
secret.DebugWith("Calculated mnemonic hash", slog.String("hash", mnemonicHash))
// Get the next available derivation index for this mnemonic
derivationIndex, err := vault.GetNextDerivationIndex(cli.fs, cli.stateDir, mnemonicHash)
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.DebugWith("Using derivation index", slog.Uint64("index", uint64(derivationIndex)))
// Derive long-term keypair from mnemonic with the appropriate index
secret.DebugWith("Deriving long-term key from mnemonic", slog.Uint64("index", uint64(derivationIndex)))
ltIdentity, err := agehd.DeriveIdentity(mnemonicStr, derivationIndex)
if err != nil {
secret.Debug("Failed to derive long-term key", "error", err)
return fmt.Errorf("failed to derive long-term key from mnemonic: %w", err)
}
// Calculate the long-term key hash
ltKeyHash := vault.ComputeDoubleSHA256([]byte(ltIdentity.String()))
secret.DebugWith("Calculated long-term key hash", slog.String("hash", ltKeyHash))
// Create the default vault
secret.Debug("Creating default vault")
vlt, err := vault.CreateVault(cli.fs, cli.stateDir, "default")
@@ -106,6 +123,20 @@ func (cli *CLIInstance) Init(cmd *cobra.Command) error {
return fmt.Errorf("failed to write long-term public key: %w", err)
}
// Save vault metadata
metadata := &vault.VaultMetadata{
Name: "default",
CreatedAt: time.Now(),
DerivationIndex: derivationIndex,
LongTermKeyHash: ltKeyHash,
MnemonicHash: mnemonicHash,
}
if err := vault.SaveVaultMetadata(cli.fs, vaultDir, metadata); 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 key hash")
// Unlock the vault with the derived long-term key
vlt.Unlock(ltIdentity)