diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 05cfbf3..ed80ef0 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -15,7 +15,8 @@ "Bash(golangci-lint run:*)", "Bash(git add:*)", "Bash(gofumpt:*)", - "Bash(git stash:*)" + "Bash(git stash:*)", + "Bash(git commit:*)" ], "deny": [] } diff --git a/internal/cli/integration_test.go b/internal/cli/integration_test.go index f9c7aa6..fb9f598 100644 --- a/internal/cli/integration_test.go +++ b/internal/cli/integration_test.go @@ -1283,6 +1283,7 @@ func test18AgeKeyOperations(t *testing.T, tempDir, secretPath, testMnemonic stri fmt.Sprintf("HOME=%s", os.Getenv("HOME")), } output, err := cmd.CombinedOutput() + return string(output), err } @@ -1349,6 +1350,7 @@ func test19DisasterRecovery(t *testing.T, tempDir, secretPath, testMnemonic stri fmt.Sprintf("HOME=%s", os.Getenv("HOME")), } output, err := cmd.CombinedOutput() + return string(output), err } @@ -1443,6 +1445,7 @@ func test20VersionTimestamps(t *testing.T, tempDir, secretPath, testMnemonic str fmt.Sprintf("HOME=%s", os.Getenv("HOME")), } output, err := cmd.CombinedOutput() + return string(output), err } @@ -2076,6 +2079,7 @@ func readFile(t *testing.T, path string) []byte { t.Helper() data, err := os.ReadFile(path) require.NoError(t, err, "Should be able to read file: %s", path) + return data } @@ -2125,6 +2129,7 @@ func copyDir(src, dst string) error { } } } + return nil } diff --git a/internal/cli/vault.go b/internal/cli/vault.go index 25a85c4..b7f6c2e 100644 --- a/internal/cli/vault.go +++ b/internal/cli/vault.go @@ -244,7 +244,7 @@ func (cli *Instance) VaultImport(cmd *cobra.Command, vaultName string) error { existingMetadata, err := vault.LoadVaultMetadata(cli.fs, vaultDir) if err != nil { // If metadata doesn't exist, create new - existingMetadata = &vault.VaultMetadata{ + existingMetadata = &vault.Metadata{ CreatedAt: time.Now(), } } diff --git a/internal/cli/version.go b/internal/cli/version.go index 82f8aa4..285ca52 100644 --- a/internal/cli/version.go +++ b/internal/cli/version.go @@ -126,6 +126,7 @@ func (cli *Instance) ListVersions(cmd *cobra.Command, secretName string) error { status = "current (error)" } fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", version, "-", status, "-", "-") + continue } @@ -192,5 +193,6 @@ func (cli *Instance) PromoteVersion(cmd *cobra.Command, secretName string, versi } cmd.Printf("Promoted version %s to current for secret '%s'\n", version, secretName) + return nil } diff --git a/internal/secret/keychainunlocker.go b/internal/secret/keychainunlocker.go index b20d864..5e71d43 100644 --- a/internal/secret/keychainunlocker.go +++ b/internal/secret/keychainunlocker.go @@ -145,6 +145,7 @@ func (k *KeychainUnlocker) GetID() string { // We cannot continue with a fallback ID as that would mask data corruption panic(fmt.Sprintf("Keychain unlocker metadata is corrupt or missing keychain item name: %v", err)) } + return fmt.Sprintf("%s-keychain", keychainItemName) } @@ -172,6 +173,7 @@ func (k *KeychainUnlocker) Remove() error { } Debug("Successfully removed keychain unlocker", "unlocker_id", k.GetID(), "keychain_item", keychainItemName) + return nil } @@ -210,6 +212,7 @@ func generateKeychainUnlockerName(vaultName string) (string, error) { // Format: secret--- enrollmentDate := time.Now().Format("2006-01-02") + return fmt.Sprintf("secret-%s-%s-%s", vaultName, hostname, enrollmentDate), nil } @@ -377,7 +380,9 @@ func CreateKeychainUnlocker(fs afero.Fs, stateDir string) (*KeychainUnlocker, er return nil, fmt.Errorf("failed to marshal unlocker metadata: %w", err) } - if err := afero.WriteFile(fs, filepath.Join(unlockerDir, "unlocker-metadata.json"), metadataBytes, FilePerms); err != nil { + if err := afero.WriteFile(fs, + filepath.Join(unlockerDir, "unlocker-metadata.json"), + metadataBytes, FilePerms); err != nil { return nil, fmt.Errorf("failed to write unlocker metadata: %w", err) } @@ -394,6 +399,7 @@ func checkMacOSAvailable() error { if err := cmd.Run(); err != nil { return fmt.Errorf("macOS security command not available: %w (keychain unlockers are only supported on macOS)", err) } + return nil } @@ -415,7 +421,7 @@ func storeInKeychain(itemName string, data []byte) error { if err := validateKeychainItemName(itemName); err != nil { return fmt.Errorf("invalid keychain item name: %w", err) } - cmd := exec.Command("/usr/bin/security", "add-generic-password", //nolint:gosec // Input validated by validateKeychainItemName + cmd := exec.Command("/usr/bin/security", "add-generic-password", //nolint:gosec "-a", itemName, "-s", itemName, "-w", string(data), @@ -434,7 +440,7 @@ func retrieveFromKeychain(itemName string) ([]byte, error) { return nil, fmt.Errorf("invalid keychain item name: %w", err) } - cmd := exec.Command("/usr/bin/security", "find-generic-password", //nolint:gosec // Input validated by validateKeychainItemName + cmd := exec.Command("/usr/bin/security", "find-generic-password", //nolint:gosec "-a", itemName, "-s", itemName, "-w") // Return password only @@ -458,7 +464,7 @@ func deleteFromKeychain(itemName string) error { return fmt.Errorf("invalid keychain item name: %w", err) } - cmd := exec.Command("/usr/bin/security", "delete-generic-password", //nolint:gosec // Input validated by validateKeychainItemName + cmd := exec.Command("/usr/bin/security", "delete-generic-password", //nolint:gosec "-a", itemName, "-s", itemName) diff --git a/internal/secret/metadata.go b/internal/secret/metadata.go index 0662798..b1996a7 100644 --- a/internal/secret/metadata.go +++ b/internal/secret/metadata.go @@ -6,11 +6,13 @@ import ( // VaultMetadata contains information about a vault type VaultMetadata struct { - CreatedAt time.Time `json:"createdAt"` - Description string `json:"description,omitempty"` - DerivationIndex uint32 `json:"derivation_index"` - PublicKeyHash string `json:"public_key_hash,omitempty"` // Double SHA256 hash of the actual long-term public key - MnemonicFamilyHash string `json:"mnemonic_family_hash,omitempty"` // Double SHA256 hash of index-0 key (for grouping vaults from same mnemonic) + CreatedAt time.Time `json:"createdAt"` + Description string `json:"description,omitempty"` + DerivationIndex uint32 `json:"derivation_index"` + // Double SHA256 hash of the actual long-term public key + PublicKeyHash string `json:"public_key_hash,omitempty"` + // Double SHA256 hash of index-0 key (for grouping vaults from same mnemonic) + MnemonicFamilyHash string `json:"mnemonic_family_hash,omitempty"` } // UnlockerMetadata contains information about an unlocker diff --git a/internal/secret/passphraseunlocker.go b/internal/secret/passphraseunlocker.go index 1cbb35c..4e87171 100644 --- a/internal/secret/passphraseunlocker.go +++ b/internal/secret/passphraseunlocker.go @@ -121,6 +121,7 @@ func (p *PassphraseUnlocker) Remove() error { if err := p.fs.RemoveAll(p.Directory); err != nil { return fmt.Errorf("failed to remove passphrase unlocker directory: %w", err) } + return nil } diff --git a/internal/secret/pgpunlocker.go b/internal/secret/pgpunlocker.go index a60ae94..20e803e 100644 --- a/internal/secret/pgpunlocker.go +++ b/internal/secret/pgpunlocker.go @@ -129,6 +129,7 @@ func (p *PGPUnlocker) GetID() string { // We cannot continue with a fallback ID as that would mask data corruption panic(fmt.Sprintf("PGP unlocker metadata is corrupt or missing GPG key ID: %v", err)) } + return fmt.Sprintf("%s-pgp", gpgKeyID) } @@ -139,6 +140,7 @@ func (p *PGPUnlocker) Remove() error { if err := p.fs.RemoveAll(p.Directory); err != nil { return fmt.Errorf("failed to remove PGP unlocker directory: %w", err) } + return nil } @@ -177,6 +179,7 @@ func generatePGPUnlockerName() (string, error) { // Format: hostname-pgp-YYYY-MM-DD enrollmentDate := time.Now().Format("2006-01-02") + return fmt.Sprintf("%s-pgp-%s", hostname, enrollmentDate), nil } @@ -320,7 +323,9 @@ func CreatePGPUnlocker(fs afero.Fs, stateDir string, gpgKeyID string) (*PGPUnloc return nil, fmt.Errorf("failed to marshal unlocker metadata: %w", err) } - if err := afero.WriteFile(fs, filepath.Join(unlockerDir, "unlocker-metadata.json"), metadataBytes, FilePerms); err != nil { + if err := afero.WriteFile(fs, + filepath.Join(unlockerDir, "unlocker-metadata.json"), + metadataBytes, FilePerms); err != nil { return nil, fmt.Errorf("failed to write unlocker metadata: %w", err) } @@ -377,6 +382,7 @@ func checkGPGAvailable() error { if err := cmd.Run(); err != nil { return fmt.Errorf("GPG not available: %w (make sure 'gpg' command is installed and in PATH)", err) } + return nil } diff --git a/internal/secret/secret.go b/internal/secret/secret.go index 7ff3842..c5b95c5 100644 --- a/internal/secret/secret.go +++ b/internal/secret/secret.go @@ -78,6 +78,7 @@ func (s *Secret) Save(value []byte, force bool) error { } Debug("Successfully saved secret", "secret_name", s.Name) + return nil } @@ -220,6 +221,7 @@ func (s *Secret) LoadMetadata() error { CreatedAt: now, UpdatedAt: now, } + return nil } @@ -278,6 +280,7 @@ func GetCurrentVault(fs afero.Fs, stateDir string) (VaultInterface, error) { if getCurrentVaultFunc == nil { return nil, fmt.Errorf("GetCurrentVault function not registered") } + return getCurrentVaultFunc(fs, stateDir) } diff --git a/internal/secret/secret_test.go b/internal/secret/secret_test.go index 7746cb3..7872047 100644 --- a/internal/secret/secret_test.go +++ b/internal/secret/secret_test.go @@ -262,6 +262,7 @@ func isValidSecretName(name string) bool { return false } } + return true } diff --git a/internal/secret/version.go b/internal/secret/version.go index 474b216..7d1ad60 100644 --- a/internal/secret/version.go +++ b/internal/secret/version.go @@ -51,6 +51,7 @@ func NewVersion(vault VaultInterface, secretName string, version string) *Versio ) now := time.Now() + return &Version{ SecretName: secretName, Version: version, @@ -219,6 +220,7 @@ func (sv *Version) Save(value []byte) error { } Debug("Successfully saved secret version", "version", sv.Version, "secret_name", sv.SecretName) + return nil } @@ -277,6 +279,7 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error { sv.Metadata = metadata Debug("Successfully loaded version metadata", "version", sv.Version) + return nil } @@ -344,6 +347,7 @@ func (sv *Version) GetValue(ltIdentity *age.X25519Identity) ([]byte, error) { "version", sv.Version, "value_length", len(value), "is_empty", len(value) == 0) + return value, nil } @@ -392,6 +396,7 @@ func GetCurrentVersion(fs afero.Fs, secretDir string) (string, error) { if len(parts) >= 2 && parts[0] == "versions" { return parts[1], nil } + return "", fmt.Errorf("invalid current version symlink format: %s", target) } } diff --git a/internal/vault/integration_version_test.go b/internal/vault/integration_version_test.go index 2f235a8..3043f2d 100644 --- a/internal/vault/integration_version_test.go +++ b/internal/vault/integration_version_test.go @@ -40,7 +40,9 @@ func TestVersionIntegrationWorkflow(t *testing.T) { stateDir := "/test/state" // Set mnemonic for testing - t.Setenv(secret.EnvMnemonic, "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about") + t.Setenv(secret.EnvMnemonic, + "abandon abandon abandon abandon abandon abandon "+ + "abandon abandon abandon abandon abandon about") // Create vault vault, err := CreateVault(fs, stateDir, "test") @@ -278,7 +280,7 @@ func TestVersionConcurrency(t *testing.T) { done := make(chan bool, 10) errors := make(chan error, 10) - for i := 0; i < 10; i++ { + for range 10 { go func() { value, err := vault.GetSecret(secretName) if err != nil { @@ -291,7 +293,7 @@ func TestVersionConcurrency(t *testing.T) { } // Wait for all goroutines - for i := 0; i < 10; i++ { + for range 10 { <-done } diff --git a/internal/vault/management.go b/internal/vault/management.go index f8aa2a4..6e8b8c2 100644 --- a/internal/vault/management.go +++ b/internal/vault/management.go @@ -26,6 +26,7 @@ func isValidVaultName(name string) bool { return false } matched, _ := regexp.MatchString(`^[a-z0-9\.\-\_]+$`, name) + return matched } @@ -85,6 +86,7 @@ func ResolveVaultSymlink(fs afero.Fs, symlinkPath string) (string, error) { } secret.Debug("resolveVaultSymlink completed successfully", "result", target) + return target, nil } } @@ -102,6 +104,7 @@ func ResolveVaultSymlink(fs afero.Fs, symlinkPath string) (string, error) { secret.Debug("Read target path from file", "target", target) secret.Debug("resolveVaultSymlink completed via fallback", "result", target) + return target, nil } @@ -250,7 +253,7 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) { } // Save vault metadata - metadata := &VaultMetadata{ + metadata := &Metadata{ CreatedAt: time.Now(), DerivationIndex: derivationIndex, PublicKeyHash: publicKeyHash, @@ -268,6 +271,7 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) { // Create and return the vault secret.Debug("Successfully created vault", "name", name) + return NewVault(fs, stateDir, name), nil } @@ -321,5 +325,6 @@ func SelectVault(fs afero.Fs, stateDir string, name string) error { } secret.Debug("Successfully selected vault", "vault_name", name) + return nil } diff --git a/internal/vault/metadata.go b/internal/vault/metadata.go index e4b3775..442ab4b 100644 --- a/internal/vault/metadata.go +++ b/internal/vault/metadata.go @@ -14,7 +14,7 @@ import ( // Alias the metadata types from secret package for convenience type ( - VaultMetadata = secret.VaultMetadata + Metadata = secret.VaultMetadata UnlockerMetadata = secret.UnlockerMetadata SecretMetadata = secret.Metadata Configuration = secret.Configuration @@ -24,6 +24,7 @@ type ( func ComputeDoubleSHA256(data []byte) string { firstHash := sha256.Sum256(data) secondHash := sha256.Sum256(firstHash[:]) + return hex.EncodeToString(secondHash[:]) } @@ -71,7 +72,7 @@ func GetNextDerivationIndex(fs afero.Fs, stateDir string, mnemonic string) (uint continue } - var metadata VaultMetadata + var metadata Metadata if err := json.Unmarshal(metadataBytes, &metadata); err != nil { // Skip vaults with invalid metadata continue @@ -93,7 +94,7 @@ func GetNextDerivationIndex(fs afero.Fs, stateDir string, mnemonic string) (uint } // SaveVaultMetadata saves vault metadata to the vault directory -func SaveVaultMetadata(fs afero.Fs, vaultDir string, metadata *VaultMetadata) error { +func SaveVaultMetadata(fs afero.Fs, vaultDir string, metadata *Metadata) error { metadataPath := filepath.Join(vaultDir, "vault-metadata.json") metadataBytes, err := json.MarshalIndent(metadata, "", " ") @@ -109,7 +110,7 @@ func SaveVaultMetadata(fs afero.Fs, vaultDir string, metadata *VaultMetadata) er } // LoadVaultMetadata loads vault metadata from the vault directory -func LoadVaultMetadata(fs afero.Fs, vaultDir string) (*VaultMetadata, error) { +func LoadVaultMetadata(fs afero.Fs, vaultDir string) (*Metadata, error) { metadataPath := filepath.Join(vaultDir, "vault-metadata.json") metadataBytes, err := afero.ReadFile(fs, metadataPath) @@ -117,7 +118,7 @@ func LoadVaultMetadata(fs afero.Fs, vaultDir string) (*VaultMetadata, error) { return nil, fmt.Errorf("failed to read vault metadata: %w", err) } - var metadata VaultMetadata + var metadata Metadata if err := json.Unmarshal(metadataBytes, &metadata); err != nil { return nil, fmt.Errorf("failed to unmarshal vault metadata: %w", err) } diff --git a/internal/vault/metadata_test.go b/internal/vault/metadata_test.go index 1897332..b16b9f2 100644 --- a/internal/vault/metadata_test.go +++ b/internal/vault/metadata_test.go @@ -68,7 +68,7 @@ func TestVaultMetadata(t *testing.T) { t.Fatalf("Failed to write public key: %v", err) } - metadata1 := &VaultMetadata{ + metadata1 := &Metadata{ DerivationIndex: 0, PublicKeyHash: pubKeyHash0, // Hash of the actual key (index 0) MnemonicFamilyHash: pubKeyHash0, // Hash of index 0 key (for family identification) @@ -117,7 +117,7 @@ func TestVaultMetadata(t *testing.T) { // Compute the hash for index 5 key pubKeyHash5 := ComputeDoubleSHA256([]byte(pubKey5)) - metadata2 := &VaultMetadata{ + metadata2 := &Metadata{ DerivationIndex: 5, PublicKeyHash: pubKeyHash5, // Hash of the actual key (index 5) MnemonicFamilyHash: pubKeyHash0, // Same family hash since it's from the same mnemonic @@ -143,7 +143,7 @@ func TestVaultMetadata(t *testing.T) { } // Create and save metadata - metadata := &VaultMetadata{ + metadata := &Metadata{ DerivationIndex: 3, PublicKeyHash: "test-public-key-hash", } diff --git a/internal/vault/secrets.go b/internal/vault/secrets.go index fff38f7..878dc2b 100644 --- a/internal/vault/secrets.go +++ b/internal/vault/secrets.go @@ -89,6 +89,7 @@ func isValidSecretName(name string) bool { // Check the basic pattern matched, _ := regexp.MatchString(`^[a-z0-9\.\-\_\/]+$`, name) + return matched } @@ -221,7 +222,10 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { return fmt.Errorf("failed to set current version: %w", err) } - secret.Debug("Successfully added secret version to vault", "secret_name", name, "version", versionName, "vault_name", v.Name) + secret.Debug("Successfully added secret version to vault", + "secret_name", name, "version", versionName, + "vault_name", v.Name) + return nil } diff --git a/internal/vault/unlockers.go b/internal/vault/unlockers.go index 93c0a77..5ec4161 100644 --- a/internal/vault/unlockers.go +++ b/internal/vault/unlockers.go @@ -39,7 +39,8 @@ func (v *Vault) GetCurrentUnlocker() (secret.Unlocker, error) { // Try to read as symlink first unlockerDir, err = linkReader.ReadlinkIfPossible(currentUnlockerPath) if err != nil { - secret.Debug("Failed to read symlink, falling back to file contents", "error", err, "symlink_path", currentUnlockerPath) + secret.Debug("Failed to read symlink, falling back to file contents", + "error", err, "symlink_path", currentUnlockerPath) // Fallback: read the path from file contents unlockerDirBytes, err := afero.ReadFile(v.fs, currentUnlockerPath) if err != nil { @@ -363,7 +364,9 @@ func (v *Vault) CreatePassphraseUnlocker(passphrase string) (*secret.PassphraseU // Write public key pubKeyPath := filepath.Join(unlockerDir, "pub.age") - if err := afero.WriteFile(v.fs, pubKeyPath, []byte(unlockerIdentity.Recipient().String()), secret.FilePerms); err != nil { + if err := afero.WriteFile(v.fs, pubKeyPath, + []byte(unlockerIdentity.Recipient().String()), + secret.FilePerms); err != nil { return nil, fmt.Errorf("failed to write unlocker public key: %w", err) } diff --git a/internal/vault/vault.go b/internal/vault/vault.go index f5ebe9f..2f93781 100644 --- a/internal/vault/vault.go +++ b/internal/vault/vault.go @@ -30,6 +30,7 @@ func NewVault(fs afero.Fs, stateDir string, name string) *Vault { longTermKey: nil, } secret.Debug("Created NewVault instance successfully") + return v } @@ -92,6 +93,7 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) { "derived_hash", derivedPubKeyHash, "stored_hash", metadata.PublicKeyHash, "derivation_index", metadata.DerivationIndex) + return nil, fmt.Errorf("derived public key does not match vault: mnemonic may be incorrect") } @@ -178,7 +180,8 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) { // Cache the derived key by unlocking the vault v.Unlock(ltIdentity) - secret.Debug("Vault is unlocked (lt key in memory) via unlocker", "vault_name", v.Name, "unlocker_type", unlocker.GetType()) + secret.Debug("Vault is unlocked (lt key in memory) via unlocker", + "vault_name", v.Name, "unlocker_type", unlocker.GetType()) return ltIdentity, nil } diff --git a/pkg/agehd/agehd.go b/pkg/agehd/agehd.go index a7ee478..480ce00 100644 --- a/pkg/agehd/agehd.go +++ b/pkg/agehd/agehd.go @@ -54,6 +54,7 @@ func IdentityFromEntropy(ent []byte) (*age.X25519Identity, error) { if err != nil { return nil, fmt.Errorf("bech32 encode: %w", err) } + return age.ParseX25519Identity(strings.ToUpper(s)) } @@ -125,6 +126,7 @@ func DeriveIdentity(mnemonic string, n uint32) (*age.X25519Identity, error) { if err != nil { return nil, err } + return IdentityFromEntropy(ent) } @@ -135,5 +137,6 @@ func DeriveIdentityFromXPRV(xprv string, n uint32) (*age.X25519Identity, error) if err != nil { return nil, err } + return IdentityFromEntropy(ent) } diff --git a/pkg/agehd/agehd_test.go b/pkg/agehd/agehd_test.go index caec4a5..9a772c2 100644 --- a/pkg/agehd/agehd_test.go +++ b/pkg/agehd/agehd_test.go @@ -393,6 +393,7 @@ func TestIdentityFromEntropyEdgeCases(t *testing.T) { err, ) // In test context, panic is acceptable for setup failures } + return b }(), expectError: false, diff --git a/pkg/bip85/bip85.go b/pkg/bip85/bip85.go index e8666ec..f0a66d6 100644 --- a/pkg/bip85/bip85.go +++ b/pkg/bip85/bip85.go @@ -45,29 +45,29 @@ var ( TestNetPrivateKey = []byte{0x04, 0x35, 0x83, 0x94} ) -// BIP85DRNG is a deterministic random number generator seeded by BIP85 entropy -type BIP85DRNG struct { +// DRNG is a deterministic random number generator seeded by BIP85 entropy +type DRNG struct { shake io.Reader } // NewBIP85DRNG creates a new DRNG seeded with BIP85 entropy -func NewBIP85DRNG(entropy []byte) *BIP85DRNG { +func NewBIP85DRNG(entropy []byte) *DRNG { // The entropy must be exactly 64 bytes (512 bits) if len(entropy) != 64 { - panic("BIP85DRNG entropy must be 64 bytes") + panic("DRNG entropy must be 64 bytes") } // Initialize SHAKE256 with the entropy shake := sha3.NewShake256() shake.Write(entropy) - return &BIP85DRNG{ + return &DRNG{ shake: shake, } } // Read implements the io.Reader interface -func (d *BIP85DRNG) Read(p []byte) (n int, err error) { +func (d *DRNG) Read(p []byte) (n int, err error) { return d.shake.Read(p) } @@ -266,6 +266,7 @@ func DeriveXPRV(masterKey *hdkeychain.ExtendedKey, index uint32) (*hdkeychain.Ex func doubleSHA256(data []byte) []byte { hash1 := sha256.Sum256(data) hash2 := sha256.Sum256(hash1[:]) + return hash2[:] }