Full self-hosted port of the Elydora tamper-evident audit protocol: - Hono API server with PostgreSQL, Redis, MinIO adapters - Ed25519 signed operations, chain hashing, Merkle epoch rollups - PBKDF2 password hashing, HMAC-SHA256 JWT, 5-role RBAC - Next.js management console - Node.js, Python, and Go SDKs - Docker Compose for local deployment - Helm chart for Kubernetes - Comprehensive security hardening and audit fixes applied Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
69 lines
1.8 KiB
TypeScript
69 lines
1.8 KiB
TypeScript
import crypto from 'node:crypto';
|
|
|
|
/**
|
|
* Generate a UUIDv7 (time-ordered UUID per RFC 9562).
|
|
*
|
|
* Layout (128 bits):
|
|
* 48-bit Unix timestamp (ms) | 4-bit version (7) | 12-bit rand_a
|
|
* 2-bit variant (10) | 62-bit rand_b
|
|
*/
|
|
export function uuidv7(): string {
|
|
const now = Date.now();
|
|
const random = crypto.randomBytes(10);
|
|
|
|
// Bytes 0-5: 48-bit timestamp (ms)
|
|
const bytes = Buffer.alloc(16);
|
|
bytes[0] = (now / 2 ** 40) & 0xff;
|
|
bytes[1] = (now / 2 ** 32) & 0xff;
|
|
bytes[2] = (now / 2 ** 24) & 0xff;
|
|
bytes[3] = (now / 2 ** 16) & 0xff;
|
|
bytes[4] = (now / 2 ** 8) & 0xff;
|
|
bytes[5] = now & 0xff;
|
|
|
|
// Bytes 6-7: version (0111) + 12 bits random
|
|
bytes[6] = 0x70 | (random[0]! & 0x0f);
|
|
bytes[7] = random[1]!;
|
|
|
|
// Bytes 8-15: variant (10) + 62 bits random
|
|
bytes[8] = 0x80 | (random[2]! & 0x3f);
|
|
bytes[9] = random[3]!;
|
|
bytes[10] = random[4]!;
|
|
bytes[11] = random[5]!;
|
|
bytes[12] = random[6]!;
|
|
bytes[13] = random[7]!;
|
|
bytes[14] = random[8]!;
|
|
bytes[15] = random[9]!;
|
|
|
|
const hex = bytes.toString('hex');
|
|
return (
|
|
hex.slice(0, 8) + '-' +
|
|
hex.slice(8, 12) + '-' +
|
|
hex.slice(12, 16) + '-' +
|
|
hex.slice(16, 20) + '-' +
|
|
hex.slice(20, 32)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Generate a random nonce: 16 random bytes, base64url encoded.
|
|
*/
|
|
export function generateNonce(): string {
|
|
const bytes = crypto.randomBytes(16);
|
|
return base64urlEncode(bytes);
|
|
}
|
|
|
|
/**
|
|
* Encode a Buffer or Uint8Array to base64url (no padding, RFC 4648 section 5).
|
|
*/
|
|
export function base64urlEncode(data: Buffer | Uint8Array): string {
|
|
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
return buf.toString('base64url');
|
|
}
|
|
|
|
/**
|
|
* Decode a base64url string (no padding) to a Buffer.
|
|
*/
|
|
export function base64urlDecode(input: string): Buffer {
|
|
return Buffer.from(input, 'base64url');
|
|
}
|