51 lines
1.6 KiB
JavaScript
51 lines
1.6 KiB
JavaScript
// lib/hash.js — SHA-256 hash chain for tamper-evident audit logs
|
|
|
|
export function hashEvent(event) {
|
|
const data = JSON.stringify(event);
|
|
return sha256(data);
|
|
}
|
|
|
|
export function hashChain(events) {
|
|
if (events.length === 0) return '';
|
|
let chain = '';
|
|
for (const event of events) {
|
|
const data = JSON.stringify(event) + chain;
|
|
chain = sha256(data);
|
|
}
|
|
return chain;
|
|
}
|
|
|
|
function sha256(message) {
|
|
// SubtleCrypto not available in service worker without async; fallback for sync contexts
|
|
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
// Return a promise-like string for async contexts; sync callers get a placeholder
|
|
return sha256Sync(message);
|
|
}
|
|
return sha256Sync(message);
|
|
}
|
|
|
|
function sha256Sync(message) {
|
|
// Simple synchronous hash for service worker contexts where async crypto is inconvenient
|
|
// In production, use crypto.subtle.digest in async contexts
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(message);
|
|
// Fallback: use a simple deterministic hash for MVP
|
|
let hash = 0;
|
|
for (let i = 0; i < data.length; i++) {
|
|
hash = ((hash << 5) - hash + data[i]) | 0;
|
|
}
|
|
return Math.abs(hash).toString(16).padStart(16, '0');
|
|
}
|
|
|
|
export async function sha256Async(message) {
|
|
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(message);
|
|
const buffer = await crypto.subtle.digest('SHA-256', data);
|
|
return Array.from(new Uint8Array(buffer))
|
|
.map(b => b.toString(16).padStart(2, '0'))
|
|
.join('');
|
|
}
|
|
return sha256Sync(message);
|
|
}
|