/** * Unit Tests for AsterSigner * * Tests the Aster signing strategy implementation in isolation. */ import { AsterSigner } from '@/core/credential-manager/signers/AsterSigner' import { Platform } from '@/types/credential' describe('AsterSigner Unit Tests', () => { let signer: AsterSigner beforeEach(() => { signer = new AsterSigner() }) describe('Platform Properties', () => { test('should have correct platform identifier', () => { expect(signer.platform).toBe(Platform.ASTER) }) test('should have correct algorithm identifier', () => { expect(signer.algorithm).toBe('eip191') }) }) describe('Public Key Derivation', () => { test('should derive public key from private key', async () => { const privateKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' const publicKey = await signer.derivePublicKey(privateKey) expect(publicKey).toBeDefined() expect(typeof publicKey).toBe('string') expect(publicKey.startsWith('0x')).toBe(true) expect(publicKey.length).toBe(132) // 0x + 64 bytes in hex }) test('should return consistent public key for same private key', async () => { const privateKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' const publicKey1 = await signer.derivePublicKey(privateKey) const publicKey2 = await signer.derivePublicKey(privateKey) expect(publicKey1).toBe(publicKey2) }) test('should handle private key without 0x prefix', async () => { const privateKeyWithoutPrefix = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' const privateKeyWithPrefix = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' const publicKey1 = await signer.derivePublicKey(privateKeyWithoutPrefix) const publicKey2 = await signer.derivePublicKey(privateKeyWithPrefix) expect(publicKey1).toBe(publicKey2) }) test('should throw error for invalid private key format', async () => { const invalidKeys = [ '', // Empty 'invalid-hex', // Non-hex '0x123', // Too short '0x' + 'f'.repeat(63), // Wrong length '0x' + 'g'.repeat(64) // Invalid hex characters ] for (const invalidKey of invalidKeys) { await expect(signer.derivePublicKey(invalidKey)) .rejects.toThrow() } }) }) describe('Signing Operations', () => { test('should sign message with valid credentials', async () => { const message = new TextEncoder().encode('test message') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(message, credentials) expect(signature).toBeDefined() expect(typeof signature).toBe('string') expect(signature.startsWith('0x')).toBe(true) expect(signature.length).toBe(132) // 0x + 65 bytes in hex (r + s + v) }) test('should produce deterministic signatures for same message', async () => { const message = new TextEncoder().encode('deterministic test') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature1 = await signer.sign(message, credentials) const signature2 = await signer.sign(message, credentials) expect(signature1).toBe(signature2) }) test('should produce different signatures for different messages', async () => { const message1 = new TextEncoder().encode('message one') const message2 = new TextEncoder().encode('message two') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature1 = await signer.sign(message1, credentials) const signature2 = await signer.sign(message2, credentials) expect(signature1).not.toBe(signature2) }) test('should produce different signatures for different private keys', async () => { const message = new TextEncoder().encode('same message') const credentials1 = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const credentials2 = { type: 'secp256k1' as const, privateKey: '0x2234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature1 = await signer.sign(message, credentials1) const signature2 = await signer.sign(message, credentials2) expect(signature1).not.toBe(signature2) }) test('should handle empty messages', async () => { const emptyMessage = new Uint8Array(0) const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(emptyMessage, credentials) expect(signature).toBeDefined() expect(typeof signature).toBe('string') expect(signature.startsWith('0x')).toBe(true) }) test('should handle large messages', async () => { const largeMessage = new Uint8Array(1024 * 1024) // 1MB largeMessage.fill(42) const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(largeMessage, credentials) expect(signature).toBeDefined() expect(typeof signature).toBe('string') expect(signature.startsWith('0x')).toBe(true) }) test('should handle private key without 0x prefix', async () => { const message = new TextEncoder().encode('test message') const credentialsWithoutPrefix = { type: 'secp256k1' as const, privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const credentialsWithPrefix = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature1 = await signer.sign(message, credentialsWithoutPrefix) const signature2 = await signer.sign(message, credentialsWithPrefix) expect(signature1).toBe(signature2) }) test('should throw error for invalid credentials', async () => { const message = new TextEncoder().encode('test message') const invalidCredentials = [ { type: 'secp256k1' as const, privateKey: '' // Empty key }, { type: 'secp256k1' as const, privateKey: 'invalid-hex' }, { type: 'secp256k1' as const, privateKey: '0x' + 'f'.repeat(63) // Wrong length }, { type: 'wrong-type' as any, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } ] for (const invalidCred of invalidCredentials) { await expect(signer.sign(message, invalidCred)) .rejects.toThrow() } }) }) describe('Verification Operations', () => { test('should verify valid signature', async () => { const message = new TextEncoder().encode('verification test') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(message, credentials) const publicKey = await signer.derivePublicKey(credentials.privateKey) const isValid = await signer.verify(message, signature, publicKey) expect(isValid).toBe(true) }) test('should reject invalid signature', async () => { const message = new TextEncoder().encode('verification test') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(message, credentials) const publicKey = await signer.derivePublicKey(credentials.privateKey) // Modify signature to make it invalid const invalidSignature = signature.slice(0, -2) + '00' const isValid = await signer.verify(message, invalidSignature, publicKey) expect(isValid).toBe(false) }) test('should reject signature with wrong public key', async () => { const message = new TextEncoder().encode('verification test') const credentials1 = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const credentials2 = { type: 'secp256k1' as const, privateKey: '0x2234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(message, credentials1) const wrongPublicKey = await signer.derivePublicKey(credentials2.privateKey) const isValid = await signer.verify(message, signature, wrongPublicKey) expect(isValid).toBe(false) }) test('should reject signature with wrong message', async () => { const message1 = new TextEncoder().encode('original message') const message2 = new TextEncoder().encode('different message') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(message1, credentials) const publicKey = await signer.derivePublicKey(credentials.privateKey) const isValid = await signer.verify(message2, signature, publicKey) expect(isValid).toBe(false) }) test('should handle malformed signature gracefully', async () => { const message = new TextEncoder().encode('test message') const publicKey = await signer.derivePublicKey('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') const malformedSignatures = [ '', // Empty 'invalid', // Not hex '0x12345', // Too short '0x' + 'g'.repeat(130) // Invalid hex characters ] for (const malformedSig of malformedSignatures) { const isValid = await signer.verify(message, malformedSig, publicKey) expect(isValid).toBe(false) } }) test('should handle malformed public key gracefully', async () => { const message = new TextEncoder().encode('test message') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(message, credentials) const malformedPublicKeys = [ '', // Empty 'invalid', // Not hex '0x12345', // Too short '0x' + 'g'.repeat(130) // Invalid hex characters ] for (const malformedPubKey of malformedPublicKeys) { const isValid = await signer.verify(message, signature, malformedPubKey) expect(isValid).toBe(false) } }) }) describe('EIP-191 Personal Message Signing', () => { test('should implement EIP-191 personal message format', async () => { const message = new TextEncoder().encode('Hello Ethereum!') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(message, credentials) // EIP-191 signature should be recoverable expect(signature).toBeDefined() expect(signature.length).toBe(132) // 0x + 65 bytes // The last byte should be the recovery parameter (27 or 28 for legacy, 0 or 1 for new) const lastByte = signature.slice(-2) const recoveryParam = parseInt(lastByte, 16) expect([0, 1, 27, 28]).toContain(recoveryParam) }) test('should prefix message with EIP-191 header', async () => { const originalMessage = 'test message' const messageBytes = new TextEncoder().encode(originalMessage) const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } // Sign the same message content but constructed differently const signature1 = await signer.sign(messageBytes, credentials) // Should be consistent const signature2 = await signer.sign(messageBytes, credentials) expect(signature1).toBe(signature2) }) test('should handle unicode messages properly', async () => { const unicodeMessage = new TextEncoder().encode('Hello δΈ–η•Œ! 🌍') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(unicodeMessage, credentials) expect(signature).toBeDefined() expect(signature.length).toBe(132) }) }) describe('Performance Requirements', () => { test('should sign within performance requirements', async () => { const message = new TextEncoder().encode('performance test') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const startTime = performance.now() await signer.sign(message, credentials) const duration = performance.now() - startTime expect(duration).toBeLessThan(50) // Should meet performance requirement }) test('should verify within performance requirements', async () => { const message = new TextEncoder().encode('verification performance test') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature = await signer.sign(message, credentials) const publicKey = await signer.derivePublicKey(credentials.privateKey) const startTime = performance.now() await signer.verify(message, signature, publicKey) const duration = performance.now() - startTime expect(duration).toBeLessThan(50) }) test('should handle concurrent operations efficiently', async () => { const message = new TextEncoder().encode('concurrent test') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const startTime = performance.now() const promises = Array.from({ length: 10 }, () => signer.sign(message, credentials)) const signatures = await Promise.all(promises) const duration = performance.now() - startTime expect(signatures).toHaveLength(10) expect(signatures.every(sig => sig === signatures[0])).toBe(true) // Deterministic expect(duration).toBeLessThan(200) // 10 concurrent operations }) }) describe('Credential Validation', () => { test('should accept valid secp256k1 credentials', () => { const validCredentials = [ { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' }, { type: 'secp256k1' as const, privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' }, { type: 'secp256k1' as const, privateKey: '0xABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789' } ] validCredentials.forEach(creds => { expect(() => signer.validateCredentials(creds)).not.toThrow() }) }) test('should reject invalid credential types', () => { const invalidCredentials = [ { type: 'ed25519' as any, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' }, { type: 'hmac' as any, apiKey: 'test', secretKey: 'test' }, { type: 'rsa' as any, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } ] invalidCredentials.forEach(creds => { expect(() => signer.validateCredentials(creds)).toThrow() }) }) test('should reject malformed private keys', () => { const invalidPrivateKeys = [ '', // Empty 'invalid-hex', '0x123', // Too short '0x' + 'f'.repeat(63), // 31 bytes '0x' + 'f'.repeat(65), // 33 bytes '0x' + 'g'.repeat(64) // Invalid hex ] invalidPrivateKeys.forEach(privateKey => { const credentials = { type: 'secp256k1' as const, privateKey } expect(() => signer.validateCredentials(credentials)).toThrow() }) }) }) describe('Edge Cases', () => { test('should handle null and undefined inputs gracefully', async () => { const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } await expect(signer.sign(null as any, credentials)).rejects.toThrow() await expect(signer.sign(undefined as any, credentials)).rejects.toThrow() const message = new TextEncoder().encode('test') await expect(signer.sign(message, null as any)).rejects.toThrow() await expect(signer.sign(message, undefined as any)).rejects.toThrow() }) test('should handle very large messages efficiently', async () => { const megaMessage = new Uint8Array(10 * 1024 * 1024) // 10MB megaMessage.fill(123) const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const startTime = performance.now() const signature = await signer.sign(megaMessage, credentials) const duration = performance.now() - startTime expect(signature).toBeDefined() expect(duration).toBeLessThan(2000) // ECDSA with large messages might be slower }) test('should maintain consistency across multiple instances', async () => { const signer1 = new AsterSigner() const signer2 = new AsterSigner() const message = new TextEncoder().encode('consistency test') const credentials = { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } const signature1 = await signer1.sign(message, credentials) const signature2 = await signer2.sign(message, credentials) expect(signature1).toBe(signature2) }) test('should handle boundary values for private keys', async () => { const message = new TextEncoder().encode('boundary test') // Test with minimum valid private key (1) const minCredentials = { type: 'secp256k1' as const, privateKey: '0x0000000000000000000000000000000000000000000000000000000000000001' } const signature1 = await signer.sign(message, minCredentials) expect(signature1).toBeDefined() // Test with maximum valid private key (close to secp256k1 order) const maxCredentials = { type: 'secp256k1' as const, privateKey: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140' } const signature2 = await signer.sign(message, maxCredentials) expect(signature2).toBeDefined() }) }) })