feat: v1→v2 wallet key migration on server startup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 10:45:06 +09:00
parent 3a75f64d44
commit 10a3f0156b
3 changed files with 62 additions and 0 deletions

View File

@@ -204,6 +204,44 @@ func (s *Service) decryptPrivKeyV2(cipherHex, nonceHex, saltHex string, userID u
return tocrypto.PrivateKey(plaintext), nil
}
// ---- Wallet Migration ----
// MigrateWalletKeys re-encrypts all v1 wallets using HKDF per-wallet keys.
// Each wallet is migrated individually; failures are logged and skipped.
func (s *Service) MigrateWalletKeys() error {
wallets, err := s.repo.FindAllByKeyVersion(1)
if err != nil {
return fmt.Errorf("query v1 wallets: %w", err)
}
if len(wallets) == 0 {
return nil
}
log.Printf("INFO: migrating %d v1 wallets to v2 (HKDF)", len(wallets))
var migrated, failed int
for _, uw := range wallets {
privKey, err := s.decryptPrivKey(uw.EncryptedPrivKey, uw.EncNonce)
if err != nil {
log.Printf("ERROR: v1 decrypt failed for walletID=%d userID=%d: %v", uw.ID, uw.UserID, err)
failed++
continue
}
cipherHex, nonceHex, saltHex, err := s.encryptPrivKeyV2(privKey, uw.UserID)
if err != nil {
log.Printf("ERROR: v2 encrypt failed for walletID=%d userID=%d: %v", uw.ID, uw.UserID, err)
failed++
continue
}
if err := s.repo.UpdateEncryption(uw.ID, cipherHex, nonceHex, saltHex, 2); err != nil {
log.Printf("ERROR: DB update failed for walletID=%d userID=%d: %v", uw.ID, uw.UserID, err)
failed++
continue
}
migrated++
}
log.Printf("INFO: wallet migration complete: %d migrated, %d failed", migrated, failed)
return nil
}
// ---- Wallet Management ----
// CreateWallet generates a new keypair, encrypts it, and stores in DB.