sign-proof
v0.2.0
Published
Lightweight TypeScript library to sign and verify structured data (JSON, text, Buffer) with Ed25519 signatures and optional schema validation.
Maintainers
Readme
📦 sign-proof
A lightweight TypeScript library for cryptographically signing and verifying JSON, text, and binary data using Ed25519 signatures.
sign-proof canonicalizes JSON deterministically, hashes the normalized payload with SHA-256, and signs the resulting hash together with proof metadata. Binary payloads such as Buffer and Uint8Array are processed as raw bytes without UTF-8 conversion.
Works in both Node.js and modern browsers.
Features
- Ed25519 digital signatures with
tweetnacl - Deterministic canonical JSON serialization before hashing/signing
- SHA-256 hashing before signature generation
- Raw binary support for
BufferandUint8Array - JSON Schema validation with
ajv - Includes timestamp (
signedAt) andmimeTypein the signed proof metadata - Clean API for signing and verifying JSON, text, and binary data
- Built-in SHA-256 hash utility
- Compatible with both Node.js and modern browser environments
Installation
npm install sign-proofHow Signing Works
For JSON payloads, sign-proof first canonicalizes the object so that equivalent objects produce the same deterministic representation regardless of property insertion order.
The signing flow is:
payload
↓
canonicalize JSON (when applicable)
↓
SHA-256
↓
payload hash + signedAt + mimeType
↓
Ed25519 signatureThis means the signature is created over a fixed-size SHA-256 digest rather than directly over the original payload.
For binary payloads (Buffer or Uint8Array), the original bytes are hashed directly without converting them to UTF-8 text.
Usage
Generate Key Pair
import { generateKeyPair } from 'sign-proof';
const { publicKey, privateKey } = generateKeyPair();Sign JSON with Schema Validation
import {
signData,
verifyData,
SignedData,
JSONSchemaType,
} from 'sign-proof';
const schema: JSONSchemaType<{
name: string;
age: number;
}> = {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' },
},
required: ['name', 'age'],
additionalProperties: false,
};
const person = { name: 'Alice', age: 30 };
const privateKey = '...'; // base64
const publicKey = '...'; // base64
const proof = signData(person, privateKey, { schema });
const signed: SignedData = { payload: person, proof };
const isValid = verifyData(signed, publicKey);
console.log('Verified?', isValid);Because JSON is canonicalized before hashing, equivalent objects can be verified even if their property order differs:
const original = {
name: 'Alice',
age: 30,
};
const proof = signData(original, privateKey);
const signed: SignedData = {
payload: {
age: 30,
name: 'Alice',
},
proof,
};
console.log(verifyData(signed, publicKey)); // trueSign Plain Text
import { signData, verifyData, SignedData } from 'sign-proof';
const text = 'Hello World!';
const privateKey = '...'; // base64
const publicKey = '...'; // base64
const proof = signData(text, privateKey);
const signed: SignedData = { payload: text, proof };
const isValid = verifyData(signed, publicKey);
console.log('Verified?', isValid);Sign Binary Data with Buffer
import { signData, verifyData, SignedData } from 'sign-proof';
const buffer = Buffer.from([0x00, 0xff, 0x42, 0x10]);
const privateKey = '...'; // base64
const publicKey = '...'; // base64
const proof = signData(buffer, privateKey);
const signed: SignedData = { payload: buffer, proof };
const isValid = verifyData(signed, publicKey);
console.log('Verified?', isValid);Binary data is hashed as raw bytes and is not converted to UTF-8 before signing or verification.
Sign Binary Data with Uint8Array
import { signData, verifyData, SignedData } from 'sign-proof';
const bytes = new Uint8Array([0x00, 0xff, 0x42, 0x10]);
const proof = signData(bytes, privateKey);
const signed: SignedData = {
payload: bytes,
proof,
};
console.log(verifyData(signed, publicKey)); // trueThis is useful when working with browser APIs such as File, Blob, and ArrayBuffer:
const file = input.files?.[0];
if (file) {
const bytes = new Uint8Array(await file.arrayBuffer());
const proof = signData(bytes, privateKey);
}Hashing Utility
import { hashData } from 'sign-proof';
const hash = hashData({ foo: 'bar' });
console.log('SHA256:', hash);JSON objects are canonicalized before hashing, and binary inputs are hashed from their original bytes.
Deterministic JSON Signing
Object key order does not affect the resulting payload hash or signature.
For example, these payloads are treated as equivalent:
const first = {
name: 'Alice',
age: 30,
};
const second = {
age: 30,
name: 'Alice',
};Canonicalization produces a stable JSON representation before SHA-256 hashing. Array order remains significant.
Proof Format
Each SignedData<T> consists of the original payload and its proof:
{
payload: T;
proof: {
signature: string; // base64-encoded Ed25519 signature
signedAt: number; // UNIX timestamp (ms)
mimeType: string; // inferred from payload
};
}The payload itself is not embedded into the signature. Instead, sign-proof derives a SHA-256 hash from the normalized payload and signs that hash together with the proof metadata.
During verification, the payload is normalized and hashed again. Verification succeeds only when the resulting digest and signed metadata reproduce a valid Ed25519 signature.
Technology Stack
- TypeScript – Static typing and generics
- tweetnacl – Ed25519 cryptographic signing and verification
- ajv – JSON Schema validation
- SHA-256 – Payload hashing before signature generation
License
MIT © 2025
Author
Made by Kyrylo Sotnykov · GitHub
