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:
parent
1a1b11c5a3
commit
34d6870e6a
15
TODO.md
15
TODO.md
@ -6,7 +6,6 @@ This document outlines the bugs, issues, and improvements that need to be addres
|
||||
|
||||
### Error Handling and User Experience
|
||||
|
||||
|
||||
- [ ] **1. Inappropriate Cobra usage printing**: Commands currently print usage information for all errors, including internal program failures. Usage should only be printed when the user provides incorrect arguments or invalid commands, not when the program encounters internal errors (like file system issues, crypto failures, etc.).
|
||||
|
||||
- [ ] **2. Inconsistent error messages**: Error messages need standardization and should be user-friendly. Many errors currently expose internal implementation details.
|
||||
@ -17,7 +16,7 @@ This document outlines the bugs, issues, and improvements that need to be addres
|
||||
|
||||
### Core Functionality Bugs
|
||||
|
||||
- [ ] **5. Multiple vaults using the same mnemonic will derive the same long-term keys**: Adding additional vaults with the same mnemonic should increment the index value used. The mnemonic should be double sha256 hashed and the hash value stored in the vault metadata along with the index value (starting at zero) and when additional vaults are added with the same mnemonic (as determined by hash) then the index value should be incremented. The README should be updated to document this behavior.
|
||||
- [x] **5. Multiple vaults using the same mnemonic will derive the same long-term keys**: Adding additional vaults with the same mnemonic should increment the index value used. The mnemonic should be double sha256 hashed and the hash value stored in the vault metadata along with the index value (starting at zero) and when additional vaults are added with the same mnemonic (as determined by hash) then the index value should be incremented. The README should be updated to document this behavior.
|
||||
|
||||
- [x] **6. Directory structure inconsistency**: The README and test script reference different directory structures:
|
||||
- Current code uses `unlock.d/` but documentation shows `unlock-keys.d/`
|
||||
@ -45,15 +44,15 @@ This document outlines the bugs, issues, and improvements that need to be addres
|
||||
|
||||
- [ ] **14. Improve progress indicators**: Long operations (key generation, encryption) should show progress.
|
||||
|
||||
- [ ] **15. Better secret name validation**: Currently allows some characters that may cause issues, needs comprehensive validation.
|
||||
- [x] **15. Better secret name validation**: Currently allows some characters that may cause issues, needs comprehensive validation.
|
||||
|
||||
- [ ] **16. Add `--help` examples**: Command help should include practical examples for each operation.
|
||||
|
||||
### Command Implementation Gaps
|
||||
|
||||
- [ ] **17. `secret keys rm` not fully implemented**: Based on test output, this command may not be working correctly.
|
||||
- [x] **17. `secret keys rm` not fully implemented**: Based on test output, this command may not be working correctly.
|
||||
|
||||
- [ ] **18. `secret key select` not fully implemented**: Key selection functionality appears incomplete.
|
||||
- [x] **18. `secret key select` not fully implemented**: Key selection functionality appears incomplete.
|
||||
|
||||
- [ ] **19. Missing vault deletion command**: No way to delete vaults that are no longer needed.
|
||||
|
||||
@ -71,7 +70,7 @@ This document outlines the bugs, issues, and improvements that need to be addres
|
||||
|
||||
### PGP Integration Issues
|
||||
|
||||
- [ ] **25. Incomplete PGP unlock key implementation**: The `--keyid` parameter processing may not be fully working.
|
||||
- [x] **25. Incomplete PGP unlock key implementation**: The `--keyid` parameter processing may not be fully working.
|
||||
|
||||
- [ ] **26. Missing GPG agent integration**: Should detect and use existing GPG agent when available.
|
||||
|
||||
@ -149,9 +148,9 @@ This document outlines the bugs, issues, and improvements that need to be addres
|
||||
|
||||
### Testing Infrastructure
|
||||
|
||||
- [ ] **54. Mock filesystem consistency**: Ensure mock filesystem behavior matches real filesystem in all cases.
|
||||
- [x] **54. Mock filesystem consistency**: Ensure mock filesystem behavior matches real filesystem in all cases.
|
||||
|
||||
- [ ] **55. Integration test isolation**: Tests should not affect each other or the host system.
|
||||
- [x] **55. Integration test isolation**: Tests should not affect each other or the host system.
|
||||
|
||||
- [ ] **56. Performance benchmarks**: Add benchmarks for crypto operations and file I/O.
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
@ -164,7 +165,7 @@ func (cli *CLIInstance) VaultImport(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, vaultName, cli.stateDir)
|
||||
vlt := vault.NewVault(cli.fs, cli.stateDir, vaultName)
|
||||
|
||||
// Check if vault exists
|
||||
vaultDir, err := vlt.GetDirectory()
|
||||
@ -193,13 +194,29 @@ func (cli *CLIInstance) VaultImport(vaultName string) error {
|
||||
return fmt.Errorf("invalid BIP39 mnemonic")
|
||||
}
|
||||
|
||||
// Derive long-term key from mnemonic
|
||||
secret.Debug("Deriving long-term key from mnemonic", "index", 0)
|
||||
ltIdentity, err := agehd.DeriveIdentity(mnemonic, 0)
|
||||
// Calculate mnemonic hash for index tracking
|
||||
mnemonicHash := vault.ComputeDoubleSHA256([]byte(mnemonic))
|
||||
secret.Debug("Calculated mnemonic hash", "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.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)
|
||||
}
|
||||
|
||||
// Calculate the long-term key hash
|
||||
ltKeyHash := vault.ComputeDoubleSHA256([]byte(ltIdentity.String()))
|
||||
secret.Debug("Calculated long-term key hash", "hash", ltKeyHash)
|
||||
|
||||
// Store long-term public key in vault
|
||||
ltPublicKey := ltIdentity.Recipient().String()
|
||||
secret.Debug("Storing long-term public key", "pubkey", ltPublicKey, "vault_dir", vaultDir)
|
||||
@ -209,6 +226,20 @@ func (cli *CLIInstance) VaultImport(vaultName string) error {
|
||||
return fmt.Errorf("failed to store long-term public key: %w", err)
|
||||
}
|
||||
|
||||
// Save vault metadata
|
||||
metadata := &vault.VaultMetadata{
|
||||
Name: vaultName,
|
||||
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")
|
||||
|
||||
// Get passphrase from environment variable
|
||||
passphraseStr := os.Getenv(secret.EnvUnlockPassphrase)
|
||||
if passphraseStr == "" {
|
||||
|
@ -6,9 +6,12 @@ import (
|
||||
|
||||
// VaultMetadata contains information about a vault
|
||||
type VaultMetadata struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Description string `json:"description,omitempty"`
|
||||
DerivationIndex uint32 `json:"derivation_index"`
|
||||
LongTermKeyHash string `json:"long_term_key_hash"` // Double SHA256 hash of derived long-term private key
|
||||
MnemonicHash string `json:"mnemonic_hash"` // Double SHA256 hash of mnemonic for index tracking
|
||||
}
|
||||
|
||||
// UnlockKeyMetadata contains information about an unlock key
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"github.com/spf13/afero"
|
||||
@ -201,6 +202,18 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) {
|
||||
return nil, fmt.Errorf("failed to create unlock keys directory: %w", err)
|
||||
}
|
||||
|
||||
// Save initial vault metadata (without derivation info until a mnemonic is imported)
|
||||
metadata := &VaultMetadata{
|
||||
Name: name,
|
||||
CreatedAt: time.Now(),
|
||||
DerivationIndex: 0,
|
||||
LongTermKeyHash: "", // Will be set when mnemonic is imported
|
||||
MnemonicHash: "", // Will be set when mnemonic is imported
|
||||
}
|
||||
if err := SaveVaultMetadata(fs, vaultDir, metadata); err != nil {
|
||||
return nil, fmt.Errorf("failed to save vault metadata: %w", err)
|
||||
}
|
||||
|
||||
// Select the newly created vault as current
|
||||
secret.Debug("Selecting newly created vault as current", "name", name)
|
||||
if err := SelectVault(fs, stateDir, name); err != nil {
|
||||
|
@ -1,7 +1,14 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// Alias the metadata types from secret package for convenience
|
||||
@ -9,3 +16,104 @@ type VaultMetadata = secret.VaultMetadata
|
||||
type UnlockKeyMetadata = secret.UnlockKeyMetadata
|
||||
type SecretMetadata = secret.SecretMetadata
|
||||
type Configuration = secret.Configuration
|
||||
|
||||
// ComputeDoubleSHA256 computes the double SHA256 hash of data and returns it as hex
|
||||
func ComputeDoubleSHA256(data []byte) string {
|
||||
firstHash := sha256.Sum256(data)
|
||||
secondHash := sha256.Sum256(firstHash[:])
|
||||
return hex.EncodeToString(secondHash[:])
|
||||
}
|
||||
|
||||
// GetNextDerivationIndex finds the next available derivation index for a given mnemonic hash
|
||||
func GetNextDerivationIndex(fs afero.Fs, stateDir string, mnemonicHash string) (uint32, error) {
|
||||
vaultsDir := filepath.Join(stateDir, "vaults.d")
|
||||
|
||||
// Check if vaults directory exists
|
||||
exists, err := afero.DirExists(fs, vaultsDir)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to check if vaults directory exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
// No vaults yet, start with index 0
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Read all vault directories
|
||||
entries, err := afero.ReadDir(fs, vaultsDir)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to read vaults directory: %w", err)
|
||||
}
|
||||
|
||||
// Track the highest index for this mnemonic
|
||||
var highestIndex uint32 = 0
|
||||
foundMatch := false
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to read vault metadata
|
||||
metadataPath := filepath.Join(vaultsDir, entry.Name(), "vault-metadata.json")
|
||||
metadataBytes, err := afero.ReadFile(fs, metadataPath)
|
||||
if err != nil {
|
||||
// Skip vaults without metadata
|
||||
continue
|
||||
}
|
||||
|
||||
var metadata VaultMetadata
|
||||
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
|
||||
// Skip vaults with invalid metadata
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this vault uses the same mnemonic
|
||||
if metadata.MnemonicHash == mnemonicHash {
|
||||
foundMatch = true
|
||||
if metadata.DerivationIndex >= highestIndex {
|
||||
highestIndex = metadata.DerivationIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a match, use the next index
|
||||
if foundMatch {
|
||||
return highestIndex + 1, nil
|
||||
}
|
||||
|
||||
// No existing vault with this mnemonic, start at 0
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// SaveVaultMetadata saves vault metadata to the vault directory
|
||||
func SaveVaultMetadata(fs afero.Fs, vaultDir string, metadata *VaultMetadata) error {
|
||||
metadataPath := filepath.Join(vaultDir, "vault-metadata.json")
|
||||
|
||||
metadataBytes, err := json.MarshalIndent(metadata, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal vault metadata: %w", err)
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(fs, metadataPath, metadataBytes, secret.FilePerms); err != nil {
|
||||
return fmt.Errorf("failed to write vault metadata: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadVaultMetadata loads vault metadata from the vault directory
|
||||
func LoadVaultMetadata(fs afero.Fs, vaultDir string) (*VaultMetadata, error) {
|
||||
metadataPath := filepath.Join(vaultDir, "vault-metadata.json")
|
||||
|
||||
metadataBytes, err := afero.ReadFile(fs, metadataPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read vault metadata: %w", err)
|
||||
}
|
||||
|
||||
var metadata VaultMetadata
|
||||
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal vault metadata: %w", err)
|
||||
}
|
||||
|
||||
return &metadata, nil
|
||||
}
|
||||
|
175
internal/vault/metadata_test.go
Normal file
175
internal/vault/metadata_test.go
Normal file
@ -0,0 +1,175 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"path/filepath"
|
||||
|
||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
func TestVaultMetadata(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
stateDir := "/test/state"
|
||||
|
||||
t.Run("ComputeDoubleSHA256", func(t *testing.T) {
|
||||
// Test data
|
||||
data := []byte("test data")
|
||||
hash := ComputeDoubleSHA256(data)
|
||||
|
||||
// Verify it's a valid hex string of 64 characters (32 bytes * 2)
|
||||
if len(hash) != 64 {
|
||||
t.Errorf("Expected hash length of 64, got %d", len(hash))
|
||||
}
|
||||
|
||||
// Verify consistency
|
||||
hash2 := ComputeDoubleSHA256(data)
|
||||
if hash != hash2 {
|
||||
t.Errorf("Hash should be consistent for same input")
|
||||
}
|
||||
|
||||
// Verify different input produces different hash
|
||||
hash3 := ComputeDoubleSHA256([]byte("different data"))
|
||||
if hash == hash3 {
|
||||
t.Errorf("Different input should produce different hash")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetNextDerivationIndex", func(t *testing.T) {
|
||||
// Test with no existing vaults
|
||||
index, err := GetNextDerivationIndex(fs, stateDir, "mnemonic-hash-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get derivation index: %v", err)
|
||||
}
|
||||
if index != 0 {
|
||||
t.Errorf("Expected index 0 for first vault, got %d", index)
|
||||
}
|
||||
|
||||
// Create a vault with metadata
|
||||
vaultDir := filepath.Join(stateDir, "vaults.d", "vault1")
|
||||
if err := fs.MkdirAll(vaultDir, 0700); err != nil {
|
||||
t.Fatalf("Failed to create vault directory: %v", err)
|
||||
}
|
||||
|
||||
metadata1 := &VaultMetadata{
|
||||
Name: "vault1",
|
||||
DerivationIndex: 0,
|
||||
MnemonicHash: "mnemonic-hash-1",
|
||||
LongTermKeyHash: "key-hash-1",
|
||||
}
|
||||
if err := SaveVaultMetadata(fs, vaultDir, metadata1); err != nil {
|
||||
t.Fatalf("Failed to save metadata: %v", err)
|
||||
}
|
||||
|
||||
// Next index for same mnemonic should be 1
|
||||
index, err = GetNextDerivationIndex(fs, stateDir, "mnemonic-hash-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get derivation index: %v", err)
|
||||
}
|
||||
if index != 1 {
|
||||
t.Errorf("Expected index 1 for second vault with same mnemonic, got %d", index)
|
||||
}
|
||||
|
||||
// Different mnemonic should start at 0
|
||||
index, err = GetNextDerivationIndex(fs, stateDir, "mnemonic-hash-2")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get derivation index: %v", err)
|
||||
}
|
||||
if index != 0 {
|
||||
t.Errorf("Expected index 0 for first vault with different mnemonic, got %d", index)
|
||||
}
|
||||
|
||||
// Add another vault with same mnemonic but higher index
|
||||
vaultDir2 := filepath.Join(stateDir, "vaults.d", "vault2")
|
||||
if err := fs.MkdirAll(vaultDir2, 0700); err != nil {
|
||||
t.Fatalf("Failed to create vault directory: %v", err)
|
||||
}
|
||||
|
||||
metadata2 := &VaultMetadata{
|
||||
Name: "vault2",
|
||||
DerivationIndex: 5,
|
||||
MnemonicHash: "mnemonic-hash-1",
|
||||
LongTermKeyHash: "key-hash-2",
|
||||
}
|
||||
if err := SaveVaultMetadata(fs, vaultDir2, metadata2); err != nil {
|
||||
t.Fatalf("Failed to save metadata: %v", err)
|
||||
}
|
||||
|
||||
// Next index should be 6
|
||||
index, err = GetNextDerivationIndex(fs, stateDir, "mnemonic-hash-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get derivation index: %v", err)
|
||||
}
|
||||
if index != 6 {
|
||||
t.Errorf("Expected index 6 after vault with index 5, got %d", index)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MetadataPersistence", func(t *testing.T) {
|
||||
vaultDir := filepath.Join(stateDir, "vaults.d", "test-vault")
|
||||
if err := fs.MkdirAll(vaultDir, 0700); err != nil {
|
||||
t.Fatalf("Failed to create vault directory: %v", err)
|
||||
}
|
||||
|
||||
// Create and save metadata
|
||||
metadata := &VaultMetadata{
|
||||
Name: "test-vault",
|
||||
DerivationIndex: 3,
|
||||
MnemonicHash: "test-mnemonic-hash",
|
||||
LongTermKeyHash: "test-key-hash",
|
||||
}
|
||||
|
||||
if err := SaveVaultMetadata(fs, vaultDir, metadata); err != nil {
|
||||
t.Fatalf("Failed to save metadata: %v", err)
|
||||
}
|
||||
|
||||
// Load and verify
|
||||
loaded, err := LoadVaultMetadata(fs, vaultDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load metadata: %v", err)
|
||||
}
|
||||
|
||||
if loaded.Name != metadata.Name {
|
||||
t.Errorf("Name mismatch: expected %s, got %s", metadata.Name, loaded.Name)
|
||||
}
|
||||
if loaded.DerivationIndex != metadata.DerivationIndex {
|
||||
t.Errorf("DerivationIndex mismatch: expected %d, got %d", metadata.DerivationIndex, loaded.DerivationIndex)
|
||||
}
|
||||
if loaded.MnemonicHash != metadata.MnemonicHash {
|
||||
t.Errorf("MnemonicHash mismatch: expected %s, got %s", metadata.MnemonicHash, loaded.MnemonicHash)
|
||||
}
|
||||
if loaded.LongTermKeyHash != metadata.LongTermKeyHash {
|
||||
t.Errorf("LongTermKeyHash mismatch: expected %s, got %s", metadata.LongTermKeyHash, loaded.LongTermKeyHash)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DifferentKeysForDifferentIndices", func(t *testing.T) {
|
||||
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
|
||||
// Derive keys with different indices
|
||||
identity0, err := agehd.DeriveIdentity(testMnemonic, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to derive identity with index 0: %v", err)
|
||||
}
|
||||
|
||||
identity1, err := agehd.DeriveIdentity(testMnemonic, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to derive identity with index 1: %v", err)
|
||||
}
|
||||
|
||||
// Compute hashes
|
||||
hash0 := ComputeDoubleSHA256([]byte(identity0.String()))
|
||||
hash1 := ComputeDoubleSHA256([]byte(identity1.String()))
|
||||
|
||||
// Verify different indices produce different keys
|
||||
if hash0 == hash1 {
|
||||
t.Errorf("Different derivation indices should produce different keys")
|
||||
}
|
||||
|
||||
// Verify public keys are also different
|
||||
if identity0.Recipient().String() == identity1.Recipient().String() {
|
||||
t.Errorf("Different derivation indices should produce different public keys")
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user