multi-platform-signing.integration.test.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /**
  2. * Integration test for multi-platform signing
  3. *
  4. * This test verifies the complete multi-platform signing workflow:
  5. * 1. Load configuration with accounts from different platforms
  6. * 2. Perform signing operations using platform-specific algorithms
  7. * 3. Verify signatures using platform-specific verification
  8. * 4. Test cross-platform compatibility and isolation
  9. *
  10. * Platforms tested:
  11. * - Pacifica (Ed25519)
  12. * - Aster (EIP-191)
  13. * - Binance (HMAC-SHA256)
  14. *
  15. * Tests MUST FAIL initially until implementation is provided.
  16. */
  17. import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
  18. import * as fs from 'fs/promises';
  19. import * as path from 'path';
  20. import * as os from 'os';
  21. // Import types (this import will fail until types are implemented)
  22. import type {
  23. ICredentialManager,
  24. SignResult,
  25. Account,
  26. Platform,
  27. ConfigFile
  28. } from '@/specs/001-credential-manager/contracts/credential-manager';
  29. describe('Multi-Platform Signing Integration Tests', () => {
  30. let credentialManager: ICredentialManager;
  31. let tempDir: string;
  32. let configPath: string;
  33. // Test messages for different platforms
  34. const testMessages = {
  35. pacifica: new TextEncoder().encode(JSON.stringify({
  36. order_type: 'market',
  37. symbol: 'BTC-USD',
  38. side: 'buy',
  39. size: '0.1',
  40. timestamp: Date.now()
  41. })),
  42. aster: new TextEncoder().encode('Transfer 100 ETH to 0x1234567890abcdef'),
  43. binance: new TextEncoder().encode('GET\n/api/v3/account\n\ntimestamp=1234567890000')
  44. };
  45. beforeEach(async () => {
  46. // This will fail until CredentialManager is implemented
  47. const { CredentialManager } = await import('@/core/credential-manager/CredentialManager');
  48. credentialManager = new CredentialManager();
  49. // Create temporary directory
  50. tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'multi-platform-test-'));
  51. configPath = path.join(tempDir, 'multi-platform-config.json');
  52. // Create multi-platform configuration
  53. const multiPlatformConfig: ConfigFile = {
  54. version: "1.0",
  55. accounts: [
  56. {
  57. id: "pacifica-test-account",
  58. platform: Platform.PACIFICA,
  59. name: "Pacifica Trading Account",
  60. credentials: {
  61. type: "ed25519",
  62. privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
  63. }
  64. },
  65. {
  66. id: "aster-test-account",
  67. platform: Platform.ASTER,
  68. name: "Aster DeFi Account",
  69. credentials: {
  70. type: "eip191",
  71. privateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
  72. }
  73. },
  74. {
  75. id: "binance-test-account",
  76. platform: Platform.BINANCE,
  77. name: "Binance Spot Account",
  78. credentials: {
  79. type: "hmac",
  80. apiKey: "test-binance-api-key",
  81. secretKey: "test-binance-secret-key"
  82. }
  83. }
  84. ]
  85. };
  86. await fs.writeFile(configPath, JSON.stringify(multiPlatformConfig, null, 2));
  87. await credentialManager.loadConfig(configPath);
  88. });
  89. afterEach(async () => {
  90. // Clean up
  91. credentialManager.stopWatching();
  92. try {
  93. await fs.rm(tempDir, { recursive: true, force: true });
  94. } catch (error) {
  95. // Ignore cleanup errors
  96. }
  97. });
  98. describe('Platform Account Loading', () => {
  99. test('should load accounts from all supported platforms', () => {
  100. // Act
  101. const allAccounts = credentialManager.listAccounts();
  102. // Assert
  103. expect(allAccounts).toHaveLength(3);
  104. const platforms = allAccounts.map(account => account.platform);
  105. expect(platforms).toContain(Platform.PACIFICA);
  106. expect(platforms).toContain(Platform.ASTER);
  107. expect(platforms).toContain(Platform.BINANCE);
  108. });
  109. test('should retrieve specific accounts by ID', () => {
  110. // Act & Assert
  111. const pacificaAccount = credentialManager.getAccount("pacifica-test-account");
  112. expect(pacificaAccount).not.toBeNull();
  113. expect(pacificaAccount!.platform).toBe(Platform.PACIFICA);
  114. expect(pacificaAccount!.credentials.type).toBe("ed25519");
  115. const asterAccount = credentialManager.getAccount("aster-test-account");
  116. expect(asterAccount).not.toBeNull();
  117. expect(asterAccount!.platform).toBe(Platform.ASTER);
  118. expect(asterAccount!.credentials.type).toBe("eip191");
  119. const binanceAccount = credentialManager.getAccount("binance-test-account");
  120. expect(binanceAccount).not.toBeNull();
  121. expect(binanceAccount!.platform).toBe(Platform.BINANCE);
  122. expect(binanceAccount!.credentials.type).toBe("hmac");
  123. });
  124. });
  125. describe('Pacifica (Ed25519) Signing', () => {
  126. test('should sign Pacifica order message successfully', async () => {
  127. // Act
  128. const result: SignResult = await credentialManager.sign(
  129. "pacifica-test-account",
  130. testMessages.pacifica
  131. );
  132. // Assert
  133. expect(result.success).toBe(true);
  134. expect(result.algorithm).toBe('ed25519');
  135. expect(typeof result.signature).toBe('string');
  136. expect(result.signature!.length).toBe(88); // base64 encoded Ed25519 signature
  137. expect(result.timestamp).toBeInstanceOf(Date);
  138. });
  139. test('should verify Pacifica signature correctly', async () => {
  140. // Arrange - create signature
  141. const signResult = await credentialManager.sign(
  142. "pacifica-test-account",
  143. testMessages.pacifica
  144. );
  145. expect(signResult.success).toBe(true);
  146. // Act - verify signature
  147. const isValid = await credentialManager.verify(
  148. "pacifica-test-account",
  149. testMessages.pacifica,
  150. signResult.signature!
  151. );
  152. // Assert
  153. expect(isValid).toBe(true);
  154. });
  155. test('should reject invalid Pacifica signature', async () => {
  156. // Act - verify invalid signature
  157. const isValid = await credentialManager.verify(
  158. "pacifica-test-account",
  159. testMessages.pacifica,
  160. "invalid-signature"
  161. );
  162. // Assert
  163. expect(isValid).toBe(false);
  164. });
  165. });
  166. describe('Aster (EIP-191) Signing', () => {
  167. test('should sign Aster message successfully', async () => {
  168. // Act
  169. const result: SignResult = await credentialManager.sign(
  170. "aster-test-account",
  171. testMessages.aster
  172. );
  173. // Assert
  174. expect(result.success).toBe(true);
  175. expect(result.algorithm).toBe('eip191');
  176. expect(typeof result.signature).toBe('string');
  177. expect(result.signature!.startsWith('0x')).toBe(true); // Ethereum-style signature
  178. expect(result.signature!.length).toBe(132); // 0x + 130 hex chars for EIP-191
  179. });
  180. test('should verify Aster signature correctly', async () => {
  181. // Arrange
  182. const signResult = await credentialManager.sign(
  183. "aster-test-account",
  184. testMessages.aster
  185. );
  186. expect(signResult.success).toBe(true);
  187. // Act
  188. const isValid = await credentialManager.verify(
  189. "aster-test-account",
  190. testMessages.aster,
  191. signResult.signature!
  192. );
  193. // Assert
  194. expect(isValid).toBe(true);
  195. });
  196. test('should handle Ethereum personal message prefix', async () => {
  197. // Arrange - message that requires Ethereum personal message prefix
  198. const personalMessage = new TextEncoder().encode("Hello, Ethereum!");
  199. // Act
  200. const result = await credentialManager.sign(
  201. "aster-test-account",
  202. personalMessage
  203. );
  204. // Assert
  205. expect(result.success).toBe(true);
  206. expect(result.algorithm).toBe('eip191');
  207. // Verify the signature
  208. const isValid = await credentialManager.verify(
  209. "aster-test-account",
  210. personalMessage,
  211. result.signature!
  212. );
  213. expect(isValid).toBe(true);
  214. });
  215. });
  216. describe('Binance (HMAC-SHA256) Signing', () => {
  217. test('should sign Binance API request successfully', async () => {
  218. // Act
  219. const result: SignResult = await credentialManager.sign(
  220. "binance-test-account",
  221. testMessages.binance
  222. );
  223. // Assert
  224. expect(result.success).toBe(true);
  225. expect(result.algorithm).toBe('hmac-sha256');
  226. expect(typeof result.signature).toBe('string');
  227. expect(result.signature!.length).toBe(64); // HMAC-SHA256 produces 64 hex chars
  228. });
  229. test('should verify Binance signature correctly', async () => {
  230. // Arrange
  231. const signResult = await credentialManager.sign(
  232. "binance-test-account",
  233. testMessages.binance
  234. );
  235. expect(signResult.success).toBe(true);
  236. // Act
  237. const isValid = await credentialManager.verify(
  238. "binance-test-account",
  239. testMessages.binance,
  240. signResult.signature!
  241. );
  242. // Assert
  243. expect(isValid).toBe(true);
  244. });
  245. test('should handle Binance API query string format', async () => {
  246. // Arrange - typical Binance API query string
  247. const queryString = "symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.1&timestamp=1234567890000";
  248. const queryMessage = new TextEncoder().encode(queryString);
  249. // Act
  250. const result = await credentialManager.sign(
  251. "binance-test-account",
  252. queryMessage
  253. );
  254. // Assert
  255. expect(result.success).toBe(true);
  256. expect(result.algorithm).toBe('hmac-sha256');
  257. // Verify signature
  258. const isValid = await credentialManager.verify(
  259. "binance-test-account",
  260. queryMessage,
  261. result.signature!
  262. );
  263. expect(isValid).toBe(true);
  264. });
  265. });
  266. describe('Cross-Platform Isolation', () => {
  267. test('should not allow signing with wrong platform account', async () => {
  268. // Act & Assert - try to sign Pacifica message with Binance account
  269. const result = await credentialManager.sign(
  270. "binance-test-account",
  271. testMessages.pacifica
  272. );
  273. // Should either fail or produce platform-appropriate signature
  274. if (result.success) {
  275. expect(result.algorithm).toBe('hmac-sha256'); // Should use Binance algorithm
  276. } else {
  277. expect(typeof result.error).toBe('string');
  278. }
  279. });
  280. test('should maintain signature isolation between platforms', async () => {
  281. // Arrange - same message signed by different platforms
  282. const commonMessage = new TextEncoder().encode("Common test message");
  283. // Act - sign with all platforms
  284. const pacificaResult = await credentialManager.sign("pacifica-test-account", commonMessage);
  285. const asterResult = await credentialManager.sign("aster-test-account", commonMessage);
  286. const binanceResult = await credentialManager.sign("binance-test-account", commonMessage);
  287. // Assert - all should succeed but with different signatures
  288. expect(pacificaResult.success).toBe(true);
  289. expect(asterResult.success).toBe(true);
  290. expect(binanceResult.success).toBe(true);
  291. expect(pacificaResult.algorithm).toBe('ed25519');
  292. expect(asterResult.algorithm).toBe('eip191');
  293. expect(binanceResult.algorithm).toBe('hmac-sha256');
  294. // Signatures should be different
  295. expect(pacificaResult.signature).not.toBe(asterResult.signature);
  296. expect(asterResult.signature).not.toBe(binanceResult.signature);
  297. expect(pacificaResult.signature).not.toBe(binanceResult.signature);
  298. });
  299. test('should reject cross-platform signature verification', async () => {
  300. // Arrange - sign with one platform
  301. const message = new TextEncoder().encode("Cross-platform test");
  302. const pacificaResult = await credentialManager.sign("pacifica-test-account", message);
  303. expect(pacificaResult.success).toBe(true);
  304. // Act - try to verify with different platform account
  305. const isValid = await credentialManager.verify(
  306. "aster-test-account", // Different platform
  307. message,
  308. pacificaResult.signature!
  309. );
  310. // Assert - should fail verification
  311. expect(isValid).toBe(false);
  312. });
  313. });
  314. describe('Concurrent Multi-Platform Operations', () => {
  315. test('should handle concurrent signing across platforms', async () => {
  316. // Arrange
  317. const concurrentOperations = [
  318. credentialManager.sign("pacifica-test-account", testMessages.pacifica),
  319. credentialManager.sign("aster-test-account", testMessages.aster),
  320. credentialManager.sign("binance-test-account", testMessages.binance),
  321. credentialManager.sign("pacifica-test-account", new TextEncoder().encode("Second Pacifica")),
  322. credentialManager.sign("aster-test-account", new TextEncoder().encode("Second Aster"))
  323. ];
  324. // Act
  325. const results = await Promise.all(concurrentOperations);
  326. // Assert - all operations should complete successfully
  327. results.forEach((result, index) => {
  328. expect(result.success).toBe(true);
  329. expect(typeof result.signature).toBe('string');
  330. });
  331. // Verify algorithms are correct
  332. expect(results[0].algorithm).toBe('ed25519'); // Pacifica
  333. expect(results[1].algorithm).toBe('eip191'); // Aster
  334. expect(results[2].algorithm).toBe('hmac-sha256'); // Binance
  335. expect(results[3].algorithm).toBe('ed25519'); // Pacifica again
  336. expect(results[4].algorithm).toBe('eip191'); // Aster again
  337. });
  338. test('should maintain performance across all platforms', async () => {
  339. // Performance requirement: signing < 50ms per operation
  340. const performanceTests = [
  341. { platform: "pacifica-test-account", message: testMessages.pacifica },
  342. { platform: "aster-test-account", message: testMessages.aster },
  343. { platform: "binance-test-account", message: testMessages.binance }
  344. ];
  345. for (const test of performanceTests) {
  346. const startTime = Date.now();
  347. const result = await credentialManager.sign(test.platform, test.message);
  348. const duration = Date.now() - startTime;
  349. expect(result.success).toBe(true);
  350. expect(duration).toBeLessThan(50); // Performance requirement
  351. }
  352. });
  353. });
  354. describe('Platform-Specific Error Handling', () => {
  355. test('should handle Pacifica-specific errors gracefully', async () => {
  356. // Arrange - invalid Pacifica message (too large)
  357. const oversizedMessage = new Uint8Array(2 * 1024 * 1024); // 2MB
  358. // Act
  359. const result = await credentialManager.sign("pacifica-test-account", oversizedMessage);
  360. // Assert
  361. expect(result.success).toBe(false);
  362. expect(typeof result.error).toBe('string');
  363. expect(result.error!).toContain('size');
  364. });
  365. test('should handle Aster-specific errors gracefully', async () => {
  366. // Arrange - test with malformed Ethereum address format if applicable
  367. const asterAccount = credentialManager.getAccount("aster-test-account");
  368. expect(asterAccount).not.toBeNull();
  369. // Act - attempt operation that might cause Aster-specific error
  370. const result = await credentialManager.sign("aster-test-account", new Uint8Array(0));
  371. // Assert - should handle gracefully
  372. expect(typeof result.success).toBe('boolean');
  373. if (!result.success) {
  374. expect(typeof result.error).toBe('string');
  375. }
  376. });
  377. test('should handle Binance-specific errors gracefully', async () => {
  378. // Arrange - empty message which might be invalid for HMAC
  379. const emptyMessage = new Uint8Array(0);
  380. // Act
  381. const result = await credentialManager.sign("binance-test-account", emptyMessage);
  382. // Assert - should handle gracefully
  383. expect(typeof result.success).toBe('boolean');
  384. if (!result.success) {
  385. expect(typeof result.error).toBe('string');
  386. }
  387. });
  388. });
  389. describe('Platform Detection and Validation', () => {
  390. test('should correctly identify platform for each account', () => {
  391. // Act & Assert
  392. const accounts = credentialManager.listAccounts();
  393. accounts.forEach(account => {
  394. switch (account.platform) {
  395. case Platform.PACIFICA:
  396. expect(account.credentials.type).toBe('ed25519');
  397. expect(account.credentials.privateKey).toMatch(/^[0-9a-f]{64}$/);
  398. break;
  399. case Platform.ASTER:
  400. expect(account.credentials.type).toBe('eip191');
  401. expect(account.credentials.privateKey).toMatch(/^0x[0-9a-f]{64}$/);
  402. break;
  403. case Platform.BINANCE:
  404. expect(account.credentials.type).toBe('hmac');
  405. expect(account.credentials.apiKey).toBeTruthy();
  406. expect(account.credentials.secretKey).toBeTruthy();
  407. break;
  408. default:
  409. fail(`Unknown platform: ${account.platform}`);
  410. }
  411. });
  412. });
  413. test('should validate credential format for each platform', () => {
  414. // This test verifies that the credential manager validates
  415. // platform-specific credential formats during loading
  416. const accounts = credentialManager.listAccounts();
  417. expect(accounts).toHaveLength(3);
  418. // All accounts should be loaded successfully, indicating valid credential formats
  419. accounts.forEach(account => {
  420. expect(account.credentials).toBeDefined();
  421. expect(typeof account.credentials.type).toBe('string');
  422. });
  423. });
  424. });
  425. });