|
@@ -0,0 +1,504 @@
|
|
|
+/**
|
|
|
+ * Pacifica签名器契约测试
|
|
|
+ *
|
|
|
+ * 验证IPacificaSigner接口的契约实现
|
|
|
+ * 测试Ed25519签名、批量操作、消息序列化等Pacifica特定功能
|
|
|
+ */
|
|
|
+
|
|
|
+import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
|
|
+import {
|
|
|
+ PacificaSigner,
|
|
|
+ PacificaSignRequest,
|
|
|
+ PacificaSignResponse,
|
|
|
+ PacificaVerifyRequest,
|
|
|
+ PacificaOrderType,
|
|
|
+ PacificaOrderMessage,
|
|
|
+ PacificaCancelMessage,
|
|
|
+ PACIFICA_CONSTANTS,
|
|
|
+ Platform,
|
|
|
+ SignResult
|
|
|
+} from '../../src/index';
|
|
|
+
|
|
|
+describe('IPacificaSigner Contract Tests', () => {
|
|
|
+ let signer: PacificaSigner;
|
|
|
+ const testAccountId = 'pacifica-signer-test-001';
|
|
|
+
|
|
|
+ beforeEach(async () => {
|
|
|
+ // 创建PacificaSigner实例并添加测试账户
|
|
|
+ const { PacificaSigner } = await import('../../src/platforms/pacifica/PacificaSigner');
|
|
|
+ signer = new PacificaSigner();
|
|
|
+
|
|
|
+ // 添加测试账户私钥(64字符十六进制)
|
|
|
+ const testPrivateKey = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
|
|
|
+ signer.addAccount(testAccountId, testPrivateKey);
|
|
|
+ });
|
|
|
+
|
|
|
+ afterEach(async () => {
|
|
|
+ // 清理资源
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('平台接口验证', () => {
|
|
|
+ test('应该正确标识Pacifica平台', () => {
|
|
|
+ expect(signer.platform).toBe(Platform.PACIFICA);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('Ed25519签名功能', () => {
|
|
|
+ test('应该能对Pacifica订单进行签名', async () => {
|
|
|
+ const orderMessage: PacificaOrderMessage = {
|
|
|
+ order_type: PacificaOrderType.LIMIT,
|
|
|
+ symbol: 'BTC-USD',
|
|
|
+ side: 'buy',
|
|
|
+ size: '0.001',
|
|
|
+ price: '65000.00',
|
|
|
+ client_id: 'test-order-001',
|
|
|
+ timestamp: Date.now(),
|
|
|
+ expiry: Date.now() + 300000
|
|
|
+ };
|
|
|
+
|
|
|
+ const messageBytes = new TextEncoder().encode(JSON.stringify(orderMessage));
|
|
|
+
|
|
|
+ const request: PacificaSignRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: messageBytes,
|
|
|
+ orderType: PacificaOrderType.LIMIT,
|
|
|
+ options: {
|
|
|
+ timeout: 30000,
|
|
|
+ includeTimestamp: true,
|
|
|
+ encoding: 'base64'
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await signer.signOrder(request);
|
|
|
+
|
|
|
+ expect(response.success).toBe(true);
|
|
|
+ expect(response.signature).toBeDefined();
|
|
|
+ expect(response.signature.length).toBe(PACIFICA_CONSTANTS.SIGNATURE_BASE64_LENGTH);
|
|
|
+ expect(response.algorithm).toBe('ed25519');
|
|
|
+ expect(response.publicKey).toBeDefined();
|
|
|
+ expect(response.publicKey.length).toBe(PACIFICA_CONSTANTS.PUBLIC_KEY_BASE58_LENGTH);
|
|
|
+ expect(response.orderType).toBe(PacificaOrderType.LIMIT);
|
|
|
+ expect(response.timestamp).toBeInstanceOf(Date);
|
|
|
+ expect(response.executionTime).toBeLessThan(50); // NFR-002: <50ms
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该能对市价单进行签名', async () => {
|
|
|
+ const orderMessage: PacificaOrderMessage = {
|
|
|
+ order_type: PacificaOrderType.MARKET,
|
|
|
+ symbol: 'ETH-USD',
|
|
|
+ side: 'sell',
|
|
|
+ size: '0.1',
|
|
|
+ client_id: 'market-order-001',
|
|
|
+ timestamp: Date.now()
|
|
|
+ };
|
|
|
+
|
|
|
+ const messageBytes = new TextEncoder().encode(JSON.stringify(orderMessage));
|
|
|
+
|
|
|
+ const request: PacificaSignRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: messageBytes,
|
|
|
+ orderType: PacificaOrderType.MARKET
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await signer.signOrder(request);
|
|
|
+
|
|
|
+ expect(response.success).toBe(true);
|
|
|
+ expect(response.algorithm).toBe('ed25519');
|
|
|
+ expect(response.orderType).toBe(PacificaOrderType.MARKET);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该能对取消订单进行签名', async () => {
|
|
|
+ const cancelMessage: PacificaCancelMessage = {
|
|
|
+ action: 'cancel',
|
|
|
+ order_type: 'cancel',
|
|
|
+ order_id: 'order-to-cancel-123',
|
|
|
+ timestamp: Date.now()
|
|
|
+ };
|
|
|
+
|
|
|
+ const messageBytes = new TextEncoder().encode(JSON.stringify(cancelMessage));
|
|
|
+
|
|
|
+ const request: PacificaSignRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: messageBytes,
|
|
|
+ orderType: PacificaOrderType.CANCEL
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await signer.signOrder(request);
|
|
|
+
|
|
|
+ expect(response.success).toBe(true);
|
|
|
+ expect(response.orderType).toBe(PacificaOrderType.CANCEL);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('签名验证功能', () => {
|
|
|
+ test('应该能验证有效的Ed25519签名', async () => {
|
|
|
+ // 首先创建一个签名
|
|
|
+ const orderMessage: PacificaOrderMessage = {
|
|
|
+ order_type: PacificaOrderType.LIMIT,
|
|
|
+ symbol: 'BTC-USD',
|
|
|
+ side: 'buy',
|
|
|
+ size: '0.001',
|
|
|
+ price: '65000.00',
|
|
|
+ client_id: 'verify-test-001',
|
|
|
+ timestamp: Date.now()
|
|
|
+ };
|
|
|
+
|
|
|
+ const messageBytes = new TextEncoder().encode(JSON.stringify(orderMessage));
|
|
|
+
|
|
|
+ const signResponse = await signer.signOrder({
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: messageBytes,
|
|
|
+ orderType: PacificaOrderType.LIMIT
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(signResponse.success).toBe(true);
|
|
|
+
|
|
|
+ // 然后验证签名
|
|
|
+ const verifyRequest: PacificaVerifyRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: messageBytes,
|
|
|
+ signature: signResponse.signature,
|
|
|
+ publicKey: signResponse.publicKey,
|
|
|
+ orderType: PacificaOrderType.LIMIT
|
|
|
+ };
|
|
|
+
|
|
|
+ const verifyResponse = await signer.verifySignature(verifyRequest);
|
|
|
+
|
|
|
+ expect(verifyResponse.success).toBe(true);
|
|
|
+ expect(verifyResponse.isValid).toBe(true);
|
|
|
+ expect(verifyResponse.algorithm).toBe('ed25519');
|
|
|
+ expect(verifyResponse.publicKey).toBe(signResponse.publicKey);
|
|
|
+ expect(verifyResponse.timestamp).toBeInstanceOf(Date);
|
|
|
+ expect(verifyResponse.verificationId).toBeDefined();
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该拒绝被篡改的消息签名', async () => {
|
|
|
+ // 创建原始签名
|
|
|
+ const originalMessage = new TextEncoder().encode('original message');
|
|
|
+ const signResponse = await signer.signOrder({
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: originalMessage,
|
|
|
+ orderType: PacificaOrderType.MARKET
|
|
|
+ });
|
|
|
+
|
|
|
+ // 尝试验证不同的消息(篡改)
|
|
|
+ const tamperedMessage = new TextEncoder().encode('tampered message');
|
|
|
+ const verifyRequest: PacificaVerifyRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: tamperedMessage,
|
|
|
+ signature: signResponse.signature,
|
|
|
+ publicKey: signResponse.publicKey
|
|
|
+ };
|
|
|
+
|
|
|
+ const verifyResponse = await signer.verifySignature(verifyRequest);
|
|
|
+
|
|
|
+ expect(verifyResponse.success).toBe(true); // 验证操作成功
|
|
|
+ expect(verifyResponse.isValid).toBe(false); // 但签名无效
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该拒绝无效格式的签名', async () => {
|
|
|
+ const message = new TextEncoder().encode('test message');
|
|
|
+ const verifyRequest: PacificaVerifyRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: message,
|
|
|
+ signature: 'invalid-signature-format',
|
|
|
+ publicKey: 'valid-looking-public-key-base58'
|
|
|
+ };
|
|
|
+
|
|
|
+ const verifyResponse = await signer.verifySignature(verifyRequest);
|
|
|
+
|
|
|
+ expect(verifyResponse.success).toBe(false);
|
|
|
+ expect(verifyResponse.error).toBeDefined();
|
|
|
+ expect(verifyResponse.error).toContain('INVALID');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('公钥管理', () => {
|
|
|
+ test('应该能获取账户公钥', async () => {
|
|
|
+ const publicKey = await signer.getPublicKey(testAccountId);
|
|
|
+
|
|
|
+ expect(publicKey).toBeDefined();
|
|
|
+ expect(typeof publicKey).toBe('string');
|
|
|
+ expect(publicKey.length).toBe(PACIFICA_CONSTANTS.PUBLIC_KEY_BASE58_LENGTH);
|
|
|
+
|
|
|
+ // 验证Base58格式(基本检查)
|
|
|
+ expect(/^[1-9A-HJ-NP-Za-km-z]+$/.test(publicKey)).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该为不存在的账户返回错误', async () => {
|
|
|
+ await expect(signer.getPublicKey('non-existent-account'))
|
|
|
+ .rejects.toThrow('ACCOUNT_NOT_FOUND');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('批量签名功能', () => {
|
|
|
+ test('应该能执行批量签名操作', async () => {
|
|
|
+ const orders: PacificaOrderMessage[] = [
|
|
|
+ {
|
|
|
+ order_type: PacificaOrderType.LIMIT,
|
|
|
+ symbol: 'BTC-USD',
|
|
|
+ side: 'buy',
|
|
|
+ size: '0.001',
|
|
|
+ price: '65000.00',
|
|
|
+ client_id: 'batch-order-1',
|
|
|
+ timestamp: Date.now()
|
|
|
+ },
|
|
|
+ {
|
|
|
+ order_type: PacificaOrderType.MARKET,
|
|
|
+ symbol: 'ETH-USD',
|
|
|
+ side: 'sell',
|
|
|
+ size: '0.1',
|
|
|
+ client_id: 'batch-order-2',
|
|
|
+ timestamp: Date.now()
|
|
|
+ },
|
|
|
+ {
|
|
|
+ action: 'cancel',
|
|
|
+ order_type: PacificaOrderType.CANCEL,
|
|
|
+ order_id: 'order-to-cancel',
|
|
|
+ timestamp: Date.now(),
|
|
|
+ side: 'buy', // Add required fields for compatibility
|
|
|
+ size: '0'
|
|
|
+ } as any
|
|
|
+ ];
|
|
|
+
|
|
|
+ const requests: PacificaSignRequest[] = orders.map((order, index) => ({
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: new TextEncoder().encode(JSON.stringify(order)),
|
|
|
+ orderType: order.order_type as PacificaOrderType
|
|
|
+ }));
|
|
|
+
|
|
|
+ const startTime = Date.now();
|
|
|
+ const responses = await signer.signBatch(requests);
|
|
|
+ const totalTime = Date.now() - startTime;
|
|
|
+
|
|
|
+ expect(responses).toHaveLength(requests.length);
|
|
|
+ expect(totalTime).toBeLessThan(300); // 批量操作应该在300ms内完成
|
|
|
+
|
|
|
+ responses.forEach((response, index) => {
|
|
|
+ expect(response.success).toBe(true);
|
|
|
+ expect(response.signature).toBeDefined();
|
|
|
+ expect(response.algorithm).toBe('ed25519');
|
|
|
+ expect(response.orderType).toBe(orders[index]?.order_type);
|
|
|
+ expect(response.executionTime).toBeLessThan(50);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证所有签名都是唯一的
|
|
|
+ const signatures = responses.map(r => r.signature);
|
|
|
+ const uniqueSignatures = new Set(signatures);
|
|
|
+ expect(uniqueSignatures.size).toBe(signatures.length);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该拒绝超过最大批量大小的请求', async () => {
|
|
|
+ const requests: PacificaSignRequest[] = Array.from(
|
|
|
+ { length: PACIFICA_CONSTANTS.MAX_BATCH_SIZE + 1 },
|
|
|
+ (_, index) => ({
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: new TextEncoder().encode(`batch message ${index}`),
|
|
|
+ orderType: PacificaOrderType.MARKET
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ await expect(signer.signBatch(requests))
|
|
|
+ .rejects.toThrow('BATCH_SIZE_EXCEEDED');
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该处理批量请求中的部分失败', async () => {
|
|
|
+ const requests: PacificaSignRequest[] = [
|
|
|
+ {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: new TextEncoder().encode('valid message 1'),
|
|
|
+ orderType: PacificaOrderType.LIMIT
|
|
|
+ },
|
|
|
+ {
|
|
|
+ accountId: 'invalid-account',
|
|
|
+ message: new TextEncoder().encode('valid message 2'),
|
|
|
+ orderType: PacificaOrderType.MARKET
|
|
|
+ },
|
|
|
+ {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: new TextEncoder().encode('valid message 3'),
|
|
|
+ orderType: PacificaOrderType.CANCEL
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ const responses = await signer.signBatch(requests);
|
|
|
+
|
|
|
+ expect(responses).toHaveLength(3);
|
|
|
+ expect(responses[0]?.success).toBe(true);
|
|
|
+ expect(responses[1]?.success).toBe(false);
|
|
|
+ expect(responses[1]?.error).toContain('ACCOUNT_NOT_FOUND');
|
|
|
+ expect(responses[2]?.success).toBe(true);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('签名选项和编码', () => {
|
|
|
+ test('应该支持不同的签名编码格式', async () => {
|
|
|
+ const message = new TextEncoder().encode('encoding test message');
|
|
|
+ const encodings: Array<'base64' | 'base58' | 'hex'> = ['base64', 'base58', 'hex'];
|
|
|
+
|
|
|
+ for (const encoding of encodings) {
|
|
|
+ const request: PacificaSignRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: message,
|
|
|
+ orderType: PacificaOrderType.MARKET,
|
|
|
+ options: { encoding }
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await signer.signOrder(request);
|
|
|
+
|
|
|
+ expect(response.success).toBe(true);
|
|
|
+ expect(response.signature).toBeDefined();
|
|
|
+
|
|
|
+ // 验证编码格式
|
|
|
+ switch (encoding) {
|
|
|
+ case 'base64':
|
|
|
+ expect(/^[A-Za-z0-9+/]+=*$/.test(response.signature)).toBe(true);
|
|
|
+ break;
|
|
|
+ case 'base58':
|
|
|
+ expect(/^[1-9A-HJ-NP-Za-km-z]+$/.test(response.signature)).toBe(true);
|
|
|
+ break;
|
|
|
+ case 'hex':
|
|
|
+ expect(/^[0-9a-fA-F]+$/.test(response.signature)).toBe(true);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该支持时间戳包含选项', async () => {
|
|
|
+ const message = new TextEncoder().encode('timestamp test message');
|
|
|
+
|
|
|
+ // 包含时间戳
|
|
|
+ const withTimestamp = await signer.signOrder({
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: message,
|
|
|
+ orderType: PacificaOrderType.LIMIT,
|
|
|
+ options: { includeTimestamp: true }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 不包含时间戳
|
|
|
+ const withoutTimestamp = await signer.signOrder({
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: message,
|
|
|
+ orderType: PacificaOrderType.LIMIT,
|
|
|
+ options: { includeTimestamp: false }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(withTimestamp.success).toBe(true);
|
|
|
+ expect(withoutTimestamp.success).toBe(true);
|
|
|
+
|
|
|
+ // 签名应该不同(因为时间戳的存在)
|
|
|
+ expect(withTimestamp.signature).not.toBe(withoutTimestamp.signature);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该遵守签名超时设置', async () => {
|
|
|
+ const message = new TextEncoder().encode('timeout test message');
|
|
|
+
|
|
|
+ // 极短超时应该失败
|
|
|
+ const shortTimeoutRequest: PacificaSignRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: message,
|
|
|
+ orderType: PacificaOrderType.MARKET,
|
|
|
+ options: { timeout: 1 } // 1ms
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await signer.signOrder(shortTimeoutRequest);
|
|
|
+
|
|
|
+ expect(response.success).toBe(false);
|
|
|
+ expect(response.error).toContain('TIMEOUT');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('消息大小限制', () => {
|
|
|
+ test('应该拒绝过大的消息', async () => {
|
|
|
+ // 创建超过最大大小的消息
|
|
|
+ const largeMessage = new Uint8Array(PACIFICA_CONSTANTS.MAX_MESSAGE_SIZE + 1);
|
|
|
+ largeMessage.fill(65); // 填充'A'字符
|
|
|
+
|
|
|
+ const request: PacificaSignRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: largeMessage,
|
|
|
+ orderType: PacificaOrderType.MARKET
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await signer.signOrder(request);
|
|
|
+
|
|
|
+ expect(response.success).toBe(false);
|
|
|
+ expect(response.error).toContain('MESSAGE_TOO_LARGE');
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该接受最大大小以内的消息', async () => {
|
|
|
+ // 创建恰好最大大小的消息
|
|
|
+ const maxSizeMessage = new Uint8Array(PACIFICA_CONSTANTS.MAX_MESSAGE_SIZE);
|
|
|
+ maxSizeMessage.fill(65);
|
|
|
+
|
|
|
+ const request: PacificaSignRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: maxSizeMessage,
|
|
|
+ orderType: PacificaOrderType.MARKET
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await signer.signOrder(request);
|
|
|
+
|
|
|
+ expect(response.success).toBe(true);
|
|
|
+ expect(response.signature).toBeDefined();
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('错误处理和边界条件', () => {
|
|
|
+ test('应该处理空消息', async () => {
|
|
|
+ const emptyMessage = new Uint8Array(0);
|
|
|
+
|
|
|
+ const request: PacificaSignRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: emptyMessage,
|
|
|
+ orderType: PacificaOrderType.MARKET
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await signer.signOrder(request);
|
|
|
+
|
|
|
+ expect(response.success).toBe(false);
|
|
|
+ expect(response.error).toContain('INVALID_MESSAGE');
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该处理无效的订单类型', async () => {
|
|
|
+ const message = new TextEncoder().encode('test message');
|
|
|
+
|
|
|
+ const request: PacificaSignRequest = {
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: message,
|
|
|
+ orderType: 'invalid_order_type' as any
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await signer.signOrder(request);
|
|
|
+
|
|
|
+ expect(response.success).toBe(false);
|
|
|
+ expect(response.error).toContain('INVALID_ORDER_TYPE');
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该在并发场景下保持稳定', async () => {
|
|
|
+ const message = new TextEncoder().encode('concurrent test message');
|
|
|
+ const concurrentRequests = 20;
|
|
|
+
|
|
|
+ const promises = Array.from({ length: concurrentRequests }, (_, index) =>
|
|
|
+ signer.signOrder({
|
|
|
+ accountId: testAccountId,
|
|
|
+ message: message,
|
|
|
+ orderType: PacificaOrderType.MARKET
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ const results = await Promise.all(promises);
|
|
|
+
|
|
|
+ results.forEach((result, index) => {
|
|
|
+ expect(result.success).toBe(true);
|
|
|
+ expect(result.signature).toBeDefined();
|
|
|
+ expect(result.executionTime).toBeLessThan(50);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证所有签名都是唯一的
|
|
|
+ const signatures = results.map(r => r.signature);
|
|
|
+ const uniqueSignatures = new Set(signatures);
|
|
|
+ expect(uniqueSignatures.size).toBe(signatures.length);
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|