AsterSigner.unit.test.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. /**
  2. * Unit Tests for AsterSigner
  3. *
  4. * Tests the Aster signing strategy implementation in isolation.
  5. */
  6. import { AsterSigner } from '@/core/credential-manager/signers/AsterSigner'
  7. import { Platform } from '@/types/credential'
  8. describe('AsterSigner Unit Tests', () => {
  9. let signer: AsterSigner
  10. beforeEach(() => {
  11. signer = new AsterSigner()
  12. })
  13. describe('Platform Properties', () => {
  14. test('should have correct platform identifier', () => {
  15. expect(signer.platform).toBe(Platform.ASTER)
  16. })
  17. test('should have correct algorithm identifier', () => {
  18. expect(signer.algorithm).toBe('eip191')
  19. })
  20. })
  21. describe('Public Key Derivation', () => {
  22. test('should derive public key from private key', async () => {
  23. const privateKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  24. const publicKey = await signer.derivePublicKey(privateKey)
  25. expect(publicKey).toBeDefined()
  26. expect(typeof publicKey).toBe('string')
  27. expect(publicKey.startsWith('0x')).toBe(true)
  28. expect(publicKey.length).toBe(132) // 0x + 64 bytes in hex
  29. })
  30. test('should return consistent public key for same private key', async () => {
  31. const privateKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  32. const publicKey1 = await signer.derivePublicKey(privateKey)
  33. const publicKey2 = await signer.derivePublicKey(privateKey)
  34. expect(publicKey1).toBe(publicKey2)
  35. })
  36. test('should handle private key without 0x prefix', async () => {
  37. const privateKeyWithoutPrefix = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  38. const privateKeyWithPrefix = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  39. const publicKey1 = await signer.derivePublicKey(privateKeyWithoutPrefix)
  40. const publicKey2 = await signer.derivePublicKey(privateKeyWithPrefix)
  41. expect(publicKey1).toBe(publicKey2)
  42. })
  43. test('should throw error for invalid private key format', async () => {
  44. const invalidKeys = [
  45. '', // Empty
  46. 'invalid-hex', // Non-hex
  47. '0x123', // Too short
  48. '0x' + 'f'.repeat(63), // Wrong length
  49. '0x' + 'g'.repeat(64) // Invalid hex characters
  50. ]
  51. for (const invalidKey of invalidKeys) {
  52. await expect(signer.derivePublicKey(invalidKey))
  53. .rejects.toThrow()
  54. }
  55. })
  56. })
  57. describe('Signing Operations', () => {
  58. test('should sign message with valid credentials', async () => {
  59. const message = new TextEncoder().encode('test message')
  60. const credentials = {
  61. type: 'secp256k1' as const,
  62. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  63. }
  64. const signature = await signer.sign(message, credentials)
  65. expect(signature).toBeDefined()
  66. expect(typeof signature).toBe('string')
  67. expect(signature.startsWith('0x')).toBe(true)
  68. expect(signature.length).toBe(132) // 0x + 65 bytes in hex (r + s + v)
  69. })
  70. test('should produce deterministic signatures for same message', async () => {
  71. const message = new TextEncoder().encode('deterministic test')
  72. const credentials = {
  73. type: 'secp256k1' as const,
  74. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  75. }
  76. const signature1 = await signer.sign(message, credentials)
  77. const signature2 = await signer.sign(message, credentials)
  78. expect(signature1).toBe(signature2)
  79. })
  80. test('should produce different signatures for different messages', async () => {
  81. const message1 = new TextEncoder().encode('message one')
  82. const message2 = new TextEncoder().encode('message two')
  83. const credentials = {
  84. type: 'secp256k1' as const,
  85. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  86. }
  87. const signature1 = await signer.sign(message1, credentials)
  88. const signature2 = await signer.sign(message2, credentials)
  89. expect(signature1).not.toBe(signature2)
  90. })
  91. test('should produce different signatures for different private keys', async () => {
  92. const message = new TextEncoder().encode('same message')
  93. const credentials1 = {
  94. type: 'secp256k1' as const,
  95. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  96. }
  97. const credentials2 = {
  98. type: 'secp256k1' as const,
  99. privateKey: '0x2234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  100. }
  101. const signature1 = await signer.sign(message, credentials1)
  102. const signature2 = await signer.sign(message, credentials2)
  103. expect(signature1).not.toBe(signature2)
  104. })
  105. test('should handle empty messages', async () => {
  106. const emptyMessage = new Uint8Array(0)
  107. const credentials = {
  108. type: 'secp256k1' as const,
  109. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  110. }
  111. const signature = await signer.sign(emptyMessage, credentials)
  112. expect(signature).toBeDefined()
  113. expect(typeof signature).toBe('string')
  114. expect(signature.startsWith('0x')).toBe(true)
  115. })
  116. test('should handle large messages', async () => {
  117. const largeMessage = new Uint8Array(1024 * 1024) // 1MB
  118. largeMessage.fill(42)
  119. const credentials = {
  120. type: 'secp256k1' as const,
  121. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  122. }
  123. const signature = await signer.sign(largeMessage, credentials)
  124. expect(signature).toBeDefined()
  125. expect(typeof signature).toBe('string')
  126. expect(signature.startsWith('0x')).toBe(true)
  127. })
  128. test('should handle private key without 0x prefix', async () => {
  129. const message = new TextEncoder().encode('test message')
  130. const credentialsWithoutPrefix = {
  131. type: 'secp256k1' as const,
  132. privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  133. }
  134. const credentialsWithPrefix = {
  135. type: 'secp256k1' as const,
  136. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  137. }
  138. const signature1 = await signer.sign(message, credentialsWithoutPrefix)
  139. const signature2 = await signer.sign(message, credentialsWithPrefix)
  140. expect(signature1).toBe(signature2)
  141. })
  142. test('should throw error for invalid credentials', async () => {
  143. const message = new TextEncoder().encode('test message')
  144. const invalidCredentials = [
  145. {
  146. type: 'secp256k1' as const,
  147. privateKey: '' // Empty key
  148. },
  149. {
  150. type: 'secp256k1' as const,
  151. privateKey: 'invalid-hex'
  152. },
  153. {
  154. type: 'secp256k1' as const,
  155. privateKey: '0x' + 'f'.repeat(63) // Wrong length
  156. },
  157. {
  158. type: 'wrong-type' as any,
  159. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  160. }
  161. ]
  162. for (const invalidCred of invalidCredentials) {
  163. await expect(signer.sign(message, invalidCred))
  164. .rejects.toThrow()
  165. }
  166. })
  167. })
  168. describe('Verification Operations', () => {
  169. test('should verify valid signature', async () => {
  170. const message = new TextEncoder().encode('verification test')
  171. const credentials = {
  172. type: 'secp256k1' as const,
  173. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  174. }
  175. const signature = await signer.sign(message, credentials)
  176. const publicKey = await signer.derivePublicKey(credentials.privateKey)
  177. const isValid = await signer.verify(message, signature, publicKey)
  178. expect(isValid).toBe(true)
  179. })
  180. test('should reject invalid signature', async () => {
  181. const message = new TextEncoder().encode('verification test')
  182. const credentials = {
  183. type: 'secp256k1' as const,
  184. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  185. }
  186. const signature = await signer.sign(message, credentials)
  187. const publicKey = await signer.derivePublicKey(credentials.privateKey)
  188. // Modify signature to make it invalid
  189. const invalidSignature = signature.slice(0, -2) + '00'
  190. const isValid = await signer.verify(message, invalidSignature, publicKey)
  191. expect(isValid).toBe(false)
  192. })
  193. test('should reject signature with wrong public key', async () => {
  194. const message = new TextEncoder().encode('verification test')
  195. const credentials1 = {
  196. type: 'secp256k1' as const,
  197. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  198. }
  199. const credentials2 = {
  200. type: 'secp256k1' as const,
  201. privateKey: '0x2234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  202. }
  203. const signature = await signer.sign(message, credentials1)
  204. const wrongPublicKey = await signer.derivePublicKey(credentials2.privateKey)
  205. const isValid = await signer.verify(message, signature, wrongPublicKey)
  206. expect(isValid).toBe(false)
  207. })
  208. test('should reject signature with wrong message', async () => {
  209. const message1 = new TextEncoder().encode('original message')
  210. const message2 = new TextEncoder().encode('different message')
  211. const credentials = {
  212. type: 'secp256k1' as const,
  213. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  214. }
  215. const signature = await signer.sign(message1, credentials)
  216. const publicKey = await signer.derivePublicKey(credentials.privateKey)
  217. const isValid = await signer.verify(message2, signature, publicKey)
  218. expect(isValid).toBe(false)
  219. })
  220. test('should handle malformed signature gracefully', async () => {
  221. const message = new TextEncoder().encode('test message')
  222. const publicKey = await signer.derivePublicKey('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef')
  223. const malformedSignatures = [
  224. '', // Empty
  225. 'invalid', // Not hex
  226. '0x12345', // Too short
  227. '0x' + 'g'.repeat(130) // Invalid hex characters
  228. ]
  229. for (const malformedSig of malformedSignatures) {
  230. const isValid = await signer.verify(message, malformedSig, publicKey)
  231. expect(isValid).toBe(false)
  232. }
  233. })
  234. test('should handle malformed public key gracefully', async () => {
  235. const message = new TextEncoder().encode('test message')
  236. const credentials = {
  237. type: 'secp256k1' as const,
  238. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  239. }
  240. const signature = await signer.sign(message, credentials)
  241. const malformedPublicKeys = [
  242. '', // Empty
  243. 'invalid', // Not hex
  244. '0x12345', // Too short
  245. '0x' + 'g'.repeat(130) // Invalid hex characters
  246. ]
  247. for (const malformedPubKey of malformedPublicKeys) {
  248. const isValid = await signer.verify(message, signature, malformedPubKey)
  249. expect(isValid).toBe(false)
  250. }
  251. })
  252. })
  253. describe('EIP-191 Personal Message Signing', () => {
  254. test('should implement EIP-191 personal message format', async () => {
  255. const message = new TextEncoder().encode('Hello Ethereum!')
  256. const credentials = {
  257. type: 'secp256k1' as const,
  258. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  259. }
  260. const signature = await signer.sign(message, credentials)
  261. // EIP-191 signature should be recoverable
  262. expect(signature).toBeDefined()
  263. expect(signature.length).toBe(132) // 0x + 65 bytes
  264. // The last byte should be the recovery parameter (27 or 28 for legacy, 0 or 1 for new)
  265. const lastByte = signature.slice(-2)
  266. const recoveryParam = parseInt(lastByte, 16)
  267. expect([0, 1, 27, 28]).toContain(recoveryParam)
  268. })
  269. test('should prefix message with EIP-191 header', async () => {
  270. const originalMessage = 'test message'
  271. const messageBytes = new TextEncoder().encode(originalMessage)
  272. const credentials = {
  273. type: 'secp256k1' as const,
  274. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  275. }
  276. // Sign the same message content but constructed differently
  277. const signature1 = await signer.sign(messageBytes, credentials)
  278. // Should be consistent
  279. const signature2 = await signer.sign(messageBytes, credentials)
  280. expect(signature1).toBe(signature2)
  281. })
  282. test('should handle unicode messages properly', async () => {
  283. const unicodeMessage = new TextEncoder().encode('Hello 世界! 🌍')
  284. const credentials = {
  285. type: 'secp256k1' as const,
  286. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  287. }
  288. const signature = await signer.sign(unicodeMessage, credentials)
  289. expect(signature).toBeDefined()
  290. expect(signature.length).toBe(132)
  291. })
  292. })
  293. describe('Performance Requirements', () => {
  294. test('should sign within performance requirements', async () => {
  295. const message = new TextEncoder().encode('performance test')
  296. const credentials = {
  297. type: 'secp256k1' as const,
  298. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  299. }
  300. const startTime = performance.now()
  301. await signer.sign(message, credentials)
  302. const duration = performance.now() - startTime
  303. expect(duration).toBeLessThan(50) // Should meet performance requirement
  304. })
  305. test('should verify within performance requirements', async () => {
  306. const message = new TextEncoder().encode('verification performance test')
  307. const credentials = {
  308. type: 'secp256k1' as const,
  309. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  310. }
  311. const signature = await signer.sign(message, credentials)
  312. const publicKey = await signer.derivePublicKey(credentials.privateKey)
  313. const startTime = performance.now()
  314. await signer.verify(message, signature, publicKey)
  315. const duration = performance.now() - startTime
  316. expect(duration).toBeLessThan(50)
  317. })
  318. test('should handle concurrent operations efficiently', async () => {
  319. const message = new TextEncoder().encode('concurrent test')
  320. const credentials = {
  321. type: 'secp256k1' as const,
  322. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  323. }
  324. const startTime = performance.now()
  325. const promises = Array.from({ length: 10 }, () => signer.sign(message, credentials))
  326. const signatures = await Promise.all(promises)
  327. const duration = performance.now() - startTime
  328. expect(signatures).toHaveLength(10)
  329. expect(signatures.every(sig => sig === signatures[0])).toBe(true) // Deterministic
  330. expect(duration).toBeLessThan(200) // 10 concurrent operations
  331. })
  332. })
  333. describe('Credential Validation', () => {
  334. test('should accept valid secp256k1 credentials', () => {
  335. const validCredentials = [
  336. {
  337. type: 'secp256k1' as const,
  338. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  339. },
  340. {
  341. type: 'secp256k1' as const,
  342. privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  343. },
  344. {
  345. type: 'secp256k1' as const,
  346. privateKey: '0xABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789'
  347. }
  348. ]
  349. validCredentials.forEach(creds => {
  350. expect(() => signer.validateCredentials(creds)).not.toThrow()
  351. })
  352. })
  353. test('should reject invalid credential types', () => {
  354. const invalidCredentials = [
  355. {
  356. type: 'ed25519' as any,
  357. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  358. },
  359. {
  360. type: 'hmac' as any,
  361. apiKey: 'test',
  362. secretKey: 'test'
  363. },
  364. {
  365. type: 'rsa' as any,
  366. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  367. }
  368. ]
  369. invalidCredentials.forEach(creds => {
  370. expect(() => signer.validateCredentials(creds)).toThrow()
  371. })
  372. })
  373. test('should reject malformed private keys', () => {
  374. const invalidPrivateKeys = [
  375. '', // Empty
  376. 'invalid-hex',
  377. '0x123', // Too short
  378. '0x' + 'f'.repeat(63), // 31 bytes
  379. '0x' + 'f'.repeat(65), // 33 bytes
  380. '0x' + 'g'.repeat(64) // Invalid hex
  381. ]
  382. invalidPrivateKeys.forEach(privateKey => {
  383. const credentials = {
  384. type: 'secp256k1' as const,
  385. privateKey
  386. }
  387. expect(() => signer.validateCredentials(credentials)).toThrow()
  388. })
  389. })
  390. })
  391. describe('Edge Cases', () => {
  392. test('should handle null and undefined inputs gracefully', async () => {
  393. const credentials = {
  394. type: 'secp256k1' as const,
  395. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  396. }
  397. await expect(signer.sign(null as any, credentials)).rejects.toThrow()
  398. await expect(signer.sign(undefined as any, credentials)).rejects.toThrow()
  399. const message = new TextEncoder().encode('test')
  400. await expect(signer.sign(message, null as any)).rejects.toThrow()
  401. await expect(signer.sign(message, undefined as any)).rejects.toThrow()
  402. })
  403. test('should handle very large messages efficiently', async () => {
  404. const megaMessage = new Uint8Array(10 * 1024 * 1024) // 10MB
  405. megaMessage.fill(123)
  406. const credentials = {
  407. type: 'secp256k1' as const,
  408. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  409. }
  410. const startTime = performance.now()
  411. const signature = await signer.sign(megaMessage, credentials)
  412. const duration = performance.now() - startTime
  413. expect(signature).toBeDefined()
  414. expect(duration).toBeLessThan(2000) // ECDSA with large messages might be slower
  415. })
  416. test('should maintain consistency across multiple instances', async () => {
  417. const signer1 = new AsterSigner()
  418. const signer2 = new AsterSigner()
  419. const message = new TextEncoder().encode('consistency test')
  420. const credentials = {
  421. type: 'secp256k1' as const,
  422. privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
  423. }
  424. const signature1 = await signer1.sign(message, credentials)
  425. const signature2 = await signer2.sign(message, credentials)
  426. expect(signature1).toBe(signature2)
  427. })
  428. test('should handle boundary values for private keys', async () => {
  429. const message = new TextEncoder().encode('boundary test')
  430. // Test with minimum valid private key (1)
  431. const minCredentials = {
  432. type: 'secp256k1' as const,
  433. privateKey: '0x0000000000000000000000000000000000000000000000000000000000000001'
  434. }
  435. const signature1 = await signer.sign(message, minCredentials)
  436. expect(signature1).toBeDefined()
  437. // Test with maximum valid private key (close to secp256k1 order)
  438. const maxCredentials = {
  439. type: 'secp256k1' as const,
  440. privateKey: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140'
  441. }
  442. const signature2 = await signer.sign(message, maxCredentials)
  443. expect(signature2).toBeDefined()
  444. })
  445. })
  446. })