import { describe, it, expect } from 'vitest';
import {
sign,
signSync,
verify,
verifySync,
getPublicKey,
getPublicKeySync,
generateKeyPair,
createSignaturePayload,
} from '../../src/crypto/signing.js';
import { testKeyPair1 } from '../fixtures/keys.js';
describe('crypto/signing', () => {
describe('generateKeyPair', () => {
it('should generate a valid keypair', () => {
const keypair = generateKeyPair();
expect(keypair.privateKey).toBeDefined();
expect(keypair.publicKey).toBeDefined();
// Base64 decode and check lengths
const privateKeyBytes = Buffer.from(keypair.privateKey, 'base64');
const publicKeyBytes = Buffer.from(keypair.publicKey, 'base64');
expect(privateKeyBytes.length).toBe(32);
expect(publicKeyBytes.length).toBe(32);
});
it('should generate unique keypairs', () => {
const keypair1 = generateKeyPair();
const keypair2 = generateKeyPair();
expect(keypair1.privateKey).not.toBe(keypair2.privateKey);
expect(keypair1.publicKey).not.toBe(keypair2.publicKey);
});
});
describe('getPublicKey', () => {
it('should derive public key from private key (async)', async () => {
const keypair = generateKeyPair();
const derivedPublicKey = await getPublicKey(keypair.privateKey);
expect(derivedPublicKey).toBe(keypair.publicKey);
});
it('should derive public key from private key (sync)', () => {
const keypair = generateKeyPair();
const derivedPublicKey = getPublicKeySync(keypair.privateKey);
expect(derivedPublicKey).toBe(keypair.publicKey);
});
});
describe('sign and verify', () => {
const message = 'Hello, World!';
it('should sign and verify a message (async)', async () => {
const keypair = generateKeyPair();
const signature = await sign(message, keypair.privateKey);
expect(signature).toBeDefined();
expect(typeof signature).toBe('string');
const isValid = await verify(message, signature, keypair.publicKey);
expect(isValid).toBe(true);
});
it('should sign and verify a message (sync)', () => {
const keypair = generateKeyPair();
const signature = signSync(message, keypair.privateKey);
expect(signature).toBeDefined();
const isValid = verifySync(message, signature, keypair.publicKey);
expect(isValid).toBe(true);
});
it('should fail verification with wrong public key', async () => {
const keypair1 = generateKeyPair();
const keypair2 = generateKeyPair();
const signature = await sign(message, keypair1.privateKey);
const isValid = await verify(message, signature, keypair2.publicKey);
expect(isValid).toBe(false);
});
it('should fail verification with tampered message', async () => {
const keypair = generateKeyPair();
const signature = await sign(message, keypair.privateKey);
const isValid = await verify('Tampered message', signature, keypair.publicKey);
expect(isValid).toBe(false);
});
it('should fail verification with invalid signature', async () => {
const keypair = generateKeyPair();
const isValid = await verify(message, 'invalid-signature', keypair.publicKey);
expect(isValid).toBe(false);
});
it('should work with test fixture keys', async () => {
const signature = await sign(message, testKeyPair1.privateKey);
const isValid = await verify(message, signature, testKeyPair1.publicKey);
expect(isValid).toBe(true);
});
it('should sign Uint8Array messages', async () => {
const keypair = generateKeyPair();
const messageBytes = new TextEncoder().encode(message);
const signature = await sign(messageBytes, keypair.privateKey);
const isValid = await verify(messageBytes, signature, keypair.publicKey);
expect(isValid).toBe(true);
});
});
describe('createSignaturePayload', () => {
it('should create consistent payload format', () => {
const timestamp = 1704067200000; // 2024-01-01T00:00:00Z
const payload = createSignaturePayload('POST', '/api/v1/test', { foo: 'bar' }, timestamp);
expect(payload).toContain('POST');
expect(payload).toContain('/api/v1/test');
expect(payload).toContain(timestamp.toString());
});
it('should handle GET requests without body', () => {
const timestamp = 1704067200000;
const payload = createSignaturePayload('GET', '/api/v1/test', undefined, timestamp);
expect(payload).toContain('GET');
expect(payload).toContain('/api/v1/test');
expect(payload).toContain(timestamp.toString());
});
it('should produce different payloads for different inputs', () => {
const timestamp = 1704067200000;
const payload1 = createSignaturePayload('POST', '/api/v1/test', { foo: 'bar' }, timestamp);
const payload2 = createSignaturePayload('POST', '/api/v1/test', { foo: 'baz' }, timestamp);
expect(payload1).not.toBe(payload2);
});
it('should normalize HTTP method to uppercase', () => {
const timestamp = 1704067200000;
const payload1 = createSignaturePayload('get', '/api/v1/test', undefined, timestamp);
const payload2 = createSignaturePayload('GET', '/api/v1/test', undefined, timestamp);
expect(payload1).toBe(payload2);
});
});
});