Signer — signSDF() / verifySIG()
The signer module provides digital signing and verification for .sdf files. Signatures use the Web Crypto API — the same cryptographic substrate available in Node.js, browsers, Bun, and Electron — with no native bindings or OpenSSL dependency.
Import
import { generateSDFKeyPair, signSDF, verifySIG } from '@etapsky/sdf-kit/signer';generateSDFKeyPair(algorithm)
function generateSDFKeyPair(algorithm: 'ECDSA-P256' | 'RSA-2048'): Promise<SDFKeyPair>Generates a new cryptographic key pair for SDF document signing. Keys are returned as PEM-encoded strings and are not persisted — store them securely in your secrets management system.
Parameters
| Parameter | Type | Description |
|---|---|---|
algorithm | 'ECDSA-P256' | 'RSA-2048' | Signing algorithm. ECDSA P-256 is preferred: shorter signatures, faster operations, equivalent security. |
Return value
interface SDFKeyPair { privateKey: string; // PEM-encoded private key (PKCS#8) publicKey: string; // PEM-encoded public key (SubjectPublicKeyInfo)}Algorithm comparison
| Algorithm | Signature size | Key size | Recommended for |
|---|---|---|---|
ECDSA-P256 | ~72 bytes | 256-bit | New deployments, performance-sensitive systems |
RSA-2048 | 256 bytes | 2048-bit | Legacy system compatibility requirements |
Example
import { generateSDFKeyPair } from '@etapsky/sdf-kit/signer';import { writeFile } from 'node:fs/promises';
const { privateKey, publicKey } = await generateSDFKeyPair('ECDSA-P256');
// Write to disk for development use// In production, store in a secrets manager — never commit private keysawait writeFile('keys/private.pem', privateKey, { mode: 0o600 });await writeFile('keys/public.pem', publicKey);
console.log('Key pair generated.');console.log(publicKey);signSDF(buffer, privateKey)
function signSDF(buffer: Buffer | Uint8Array, privateKey: string): Promise<Buffer>Signs an .sdf file and returns a new buffer containing the original archive plus a signature.sig entry. The original file is not modified.
The signature covers the canonical representation of meta.json and data.json. Changes to either file after signing will invalidate the signature.
Parameters
| Parameter | Type | Description |
|---|---|---|
buffer | Buffer | Uint8Array | Raw bytes of a valid .sdf file |
privateKey | string | PEM-encoded private key (PKCS#8), as produced by generateSDFKeyPair() |
Return value
Promise<Buffer> — a new .sdf buffer with signature.sig added to the archive.
Throws
| Error code | Cause |
|---|---|
SDF_ERROR_NOT_ZIP | The buffer is not a valid .sdf archive |
SDF_ERROR_MISSING_FILE | The archive is missing required entries |
SDF_ERROR_INVALID_META | meta.json is malformed |
verifySIG(buffer, publicKey)
function verifySIG(buffer: Buffer | Uint8Array, publicKey: string): Promise<boolean>Verifies the digital signature of a signed .sdf file. Returns true if the signature is valid and the document has not been tampered with; false otherwise.
Parameters
| Parameter | Type | Description |
|---|---|---|
buffer | Buffer | Uint8Array | Raw bytes of a signed .sdf file |
publicKey | string | PEM-encoded public key (SubjectPublicKeyInfo) corresponding to the private key used for signing |
Return value
Promise<boolean> — true if the signature is valid, false if invalid or absent.
Throws
| Error code | Cause |
|---|---|
SDF_ERROR_NOT_ZIP | The buffer is not a valid .sdf archive |
SDF_ERROR_INVALID_SIGNATURE | The signature.sig entry is present but structurally malformed (distinct from a valid-but-wrong signature, which returns false) |
Complete sign and verify example
import { buildSDF } from '@etapsky/sdf-kit/producer';import { generateSDFKeyPair, signSDF, verifySIG } from '@etapsky/sdf-kit/signer';import { SDFError } from '@etapsky/sdf-kit';import { writeFile, readFile } from 'node:fs/promises';
// 1. Generate key pair (do this once; store securely)const { privateKey, publicKey } = await generateSDFKeyPair('ECDSA-P256');
// 2. Produce the SDF fileconst buffer = await buildSDF({ data, schema, issuer: 'Acme Supplies GmbH', documentType: 'invoice',});
// 3. Sign itconst signedBuffer = await signSDF(buffer, privateKey);
// 4. Write the signed fileawait writeFile('invoice-signed.sdf', signedBuffer);
// 5. Later: verify the signature before processingconst fileBuffer = await readFile('invoice-signed.sdf');const isValid = await verifySIG(fileBuffer, publicKey);
if (isValid) { console.log('Signature is valid. Document is authentic and untampered.');} else { console.error('Signature verification failed. Document may have been modified.'); process.exit(1);}Key storage recommendations
Private keys grant the ability to sign documents on behalf of the issuer. Store them with the same care as database credentials or API secrets.
Development:
SDF_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGHAgEA..."SDF_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMFkwEwYHKo..."import { signSDF } from '@etapsky/sdf-kit/signer';
const privateKey = process.env.SDF_PRIVATE_KEY;if (!privateKey) throw new Error('SDF_PRIVATE_KEY is not set');
const signedBuffer = await signSDF(buffer, privateKey);Production:
- Store private keys in AWS Secrets Manager, HashiCorp Vault, or an equivalent secrets management system.
- Grant read access only to the process that performs signing.
- Rotate keys periodically. The
signing_keystable insdf-server-coretracks key rotation via thekey_idandis_activefields. - Never commit private keys to version control.
- The
private_key_encfield in the database stores keys encrypted with AES-256-GCM. The encryption key is theKEY_ENCRYPTION_SECRETenvironment variable — never stored in the database.
Distributing the public key:
Public keys can be shared freely. Common distribution mechanisms:
- Include in API responses (e.g.
GET /admin/tenants/:id/public-key) - Publish to a well-known URL for automated consumer verification
- Include in the
meta.jsonissuer_idfield for cross-reference