@synet/credential
Version:
VC Credentials - Simple, Robust, Unit-based Verifiable Credentials service
100 lines (88 loc) • 3.28 kB
text/typescript
import { createHash, randomBytes } from "node:crypto";
/**
* Generate a CUID2-like identifier using Node.js crypto
*
* This is a simplified, zero-dependency implementation of CUID2 concepts:
* - Uses native Node.js crypto instead of @noble/hashes
* - Maintains similar structure: letter + hash of (time + entropy + counter)
* - Provides collision-resistant, sortable, URL-safe IDs
*
* @param length - Length of the generated ID (default: 24)
* @returns A CUID2-like identifier string
*/
export function createId(length = 24): string {
// Start with a random letter (a-z)
const firstLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
// Create entropy components
const time = Date.now().toString(36);
const entropy = randomBytes(8).toString("hex");
const counter = Math.floor(Math.random() * 0xffffff).toString(36);
// Combine and hash
const input = `${time}${entropy}${counter}`;
const hash = createHash("sha3-512").update(input).digest("hex");
// Convert to base36 and take required length
const hashBigInt = BigInt(`0x${hash}`);
const base36Hash = hashBigInt.toString(36);
// Combine first letter with hash, ensuring we get the right length
return (firstLetter + base36Hash).substring(0, length);
}
/**
* Base64url encoding utilities (simplified)
*/
export function base64urlEncode(data: string): string {
try {
// Try Node.js Buffer first
const nodeBuffer = (globalThis as Record<string, unknown>)?.Buffer;
if (nodeBuffer && typeof nodeBuffer === "object" && "from" in nodeBuffer) {
return (
nodeBuffer as {
from: (data: string) => { toString: (encoding: string) => string };
}
)
.from(data)
.toString("base64url");
}
// Fallback to browser btoa
const browserBtoa = (globalThis as Record<string, unknown>)?.btoa;
if (browserBtoa && typeof browserBtoa === "function") {
return (browserBtoa as (data: string) => string)(data)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
}
throw new Error("No base64 encoding available");
} catch (error) {
throw new Error(
`Base64 encoding failed: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
export function base64urlDecode(data: string): string {
try {
// Try Node.js Buffer first
const nodeBuffer = (globalThis as Record<string, unknown>)?.Buffer;
if (nodeBuffer && typeof nodeBuffer === "object" && "from" in nodeBuffer) {
return (
nodeBuffer as {
from: (data: string, encoding: string) => { toString: () => string };
}
)
.from(data, "base64url")
.toString();
}
// Fallback to browser atob
const browserAtob = (globalThis as Record<string, unknown>)?.atob;
if (browserAtob && typeof browserAtob === "function") {
let base64 = data.replace(/-/g, "+").replace(/_/g, "/");
while (base64.length % 4) {
base64 += "=";
}
return (browserAtob as (data: string) => string)(base64);
}
throw new Error("No base64 decoding available");
} catch (error) {
throw new Error(
`Base64 decoding failed: ${error instanceof Error ? error.message : String(error)}`,
);
}
}