import { describe, expect, it } from 'vitest'; import { fromBase64Url } from '../src/bytes.js'; import { contentEventPayload, frameManifestHash, generateSigningKeyPair, signStatement, verifyStatement } from '../src/signatures.js'; import { h, vectors } from './helpers.js'; describe('协议签名原语', () => { it('跨语言固定向量可以验签,且类型和负载均受签名保护', async () => { const fixture = vectors.signatures; const publicKey = await crypto.subtle.importKey('jwk', { kty: 'EC', crv: 'P-256', x: fixture.x, y: fixture.y, ext: true, }, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']); const payload = h(fixture.payload); const signature = h(fixture.signature); expect(await verifyStatement(publicKey, fixture.kind, payload, signature)).toBe(true); expect(await verifyStatement(publicKey, 'chat.edit', payload, signature)).toBe(false); payload[0] ^= 1; expect(await verifyStatement(publicKey, fixture.kind, payload, signature)).toBe(false); expect(await verifyStatement(publicKey, fixture.kind, h(fixture.payload), signature.subarray(1))).toBe(false); expect(fromBase64Url(fixture.x)).toHaveLength(32); }); it('浏览器签名可验证,篡改被拒绝', async () => { const pair = await generateSigningKeyPair(); expect(pair.privateKey.extractable).toBe(false); const payload = new TextEncoder().encode('signed command bytes'); const signature = await signStatement(pair.privateKey, 'workspace.channel.create', payload); expect(signature).toHaveLength(64); expect(await verifyStatement(pair.publicKey, 'workspace.channel.create', payload, signature)).toBe(true); expect(await verifyStatement(pair.publicKey, 'workspace.group.create', payload, signature)).toBe(false); await expect(signStatement(pair.privateKey, 'Workspace.Channel.Create', payload)).rejects.toThrow(); }); it('聊天/文件签名上下文与帧清单跨语言向量一致', async () => { const event = vectors.content_event; const manifest = await frameManifestHash(event.frame_digests.map((digest: string) => h(digest))); expect(manifest).toEqual(h(event.manifest_hash)); expect(contentEventPayload({ spaceId: h(event.space_id), channelId: event.channel_id, username: event.username, deviceId: event.device_id, eventId: h(event.event_id), eventKind: event.kind, domain: event.domain, epoch: event.epoch, itemId: h(event.item_id), frameCount: event.frame_count, manifestHash: manifest, targetItemId: h(event.target_item_id), })).toEqual(h(event.payload)); }); });