package chain import ( "encoding/hex" "testing" tocrypto "github.com/tolelom/tolchain/crypto" ) // testEncKey returns a valid 32-byte AES-256 key for testing. func testEncKey() []byte { key := make([]byte, 32) for i := range key { key[i] = byte(i) } return key } // newTestService creates a minimal Service with only the encryption key set. // No DB, Redis, or chain client — only suitable for testing pure crypto functions. func newTestService() *Service { return &Service{ encKeyBytes: testEncKey(), } } func TestEncryptDecryptRoundTrip(t *testing.T) { svc := newTestService() // Generate a real ed25519 private key privKey, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair: %v", err) } // Encrypt cipherHex, nonceHex, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey failed: %v", err) } if cipherHex == "" || nonceHex == "" { t.Fatal("encryptPrivKey returned empty strings") } // Verify ciphertext is valid hex if _, err := hex.DecodeString(cipherHex); err != nil { t.Errorf("cipherHex is not valid hex: %v", err) } if _, err := hex.DecodeString(nonceHex); err != nil { t.Errorf("nonceHex is not valid hex: %v", err) } // Decrypt decrypted, err := svc.decryptPrivKey(cipherHex, nonceHex) if err != nil { t.Fatalf("decryptPrivKey failed: %v", err) } // Compare if hex.EncodeToString(decrypted) != hex.EncodeToString(privKey) { t.Error("decrypted key does not match original") } } func TestEncryptDecrypt_DifferentKeysProduceDifferentCiphertext(t *testing.T) { svc := newTestService() privKey1, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair 1: %v", err) } privKey2, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair 2: %v", err) } cipher1, _, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey1)) if err != nil { t.Fatalf("encryptPrivKey 1 failed: %v", err) } cipher2, _, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey2)) if err != nil { t.Fatalf("encryptPrivKey 2 failed: %v", err) } if cipher1 == cipher2 { t.Error("different private keys should produce different ciphertexts") } } func TestEncryptSameKey_DifferentNonces(t *testing.T) { svc := newTestService() privKey, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair: %v", err) } cipher1, nonce1, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey 1 failed: %v", err) } cipher2, nonce2, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey 2 failed: %v", err) } // Each encryption should use a different random nonce if nonce1 == nonce2 { t.Error("encrypting the same key twice should use different nonces") } // So ciphertext should also differ (AES-GCM is nonce-dependent) if cipher1 == cipher2 { t.Error("encrypting the same key with different nonces should produce different ciphertexts") } } func TestDecryptWithWrongKey(t *testing.T) { svc := newTestService() privKey, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair: %v", err) } cipherHex, nonceHex, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey failed: %v", err) } // Create a service with a different encryption key wrongKey := make([]byte, 32) for i := range wrongKey { wrongKey[i] = byte(255 - i) } wrongSvc := &Service{encKeyBytes: wrongKey} _, err = wrongSvc.decryptPrivKey(cipherHex, nonceHex) if err == nil { t.Error("decryptPrivKey with wrong key should fail") } } func TestDecryptWithInvalidHex(t *testing.T) { svc := newTestService() _, err := svc.decryptPrivKey("not-hex", "also-not-hex") if err == nil { t.Error("decryptPrivKey with invalid hex should fail") } } func TestDecryptWithTamperedCiphertext(t *testing.T) { svc := newTestService() privKey, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair: %v", err) } cipherHex, nonceHex, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey failed: %v", err) } // Tamper with the ciphertext by flipping a byte cipherBytes, _ := hex.DecodeString(cipherHex) cipherBytes[0] ^= 0xFF tamperedHex := hex.EncodeToString(cipherBytes) _, err = svc.decryptPrivKey(tamperedHex, nonceHex) if err == nil { t.Error("decryptPrivKey with tampered ciphertext should fail") } } func TestNewService_InvalidEncryptionKey(t *testing.T) { tests := []struct { name string encKey string }{ {"too short", "aabbccdd"}, {"not hex", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"}, {"empty", ""}, {"odd length", "aabbccddeeff00112233445566778899aabbccddeeff0011223344556677889"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := NewService(nil, nil, "test-chain", "", tt.encKey) if err == nil { t.Error("NewService should fail with invalid encryption key") } }) } } func TestNewService_ValidEncryptionKey(t *testing.T) { // 64 hex chars = 32 bytes validKey := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" svc, err := NewService(nil, nil, "test-chain", "", validKey) if err != nil { t.Fatalf("NewService with valid key should succeed: %v", err) } if svc == nil { t.Fatal("NewService returned nil service") } if svc.chainID != "test-chain" { t.Errorf("chainID = %q, want %q", svc.chainID, "test-chain") } // No operator key provided, so operatorWallet should be nil if svc.operatorWallet != nil { t.Error("operatorWallet should be nil when no operator key is provided") } } func TestEnsureOperator_NilWallet(t *testing.T) { svc := newTestService() err := svc.ensureOperator() if err == nil { t.Error("ensureOperator should fail when operatorWallet is nil") } } func TestResolveUsername_NoResolver(t *testing.T) { svc := newTestService() _, err := svc.resolveUsername("testuser") if err == nil { t.Error("resolveUsername should fail when userResolver is nil") } }