// Content-addressable hashing for GEP assets. // Provides canonical JSON serialization and SHA-256 based asset IDs. // This enables deduplication, tamper detection, and cross-node consistency. const crypto = require('crypto'); // Schema version for all GEP asset types. // Bump MINOR for additive fields; MAJOR for breaking changes. const SCHEMA_VERSION = '1.5.0'; // Canonical JSON: deterministic serialization with sorted keys at all levels. // Arrays preserve order; non-finite numbers become null; undefined becomes null. function canonicalize(obj) { if (obj === null || obj === undefined) return 'null'; if (typeof obj === 'boolean') return obj ? 'true' : 'false'; if (typeof obj === 'number') { if (!Number.isFinite(obj)) return 'null'; return String(obj); } if (typeof obj === 'string') return JSON.stringify(obj); if (Array.isArray(obj)) { return '[' + obj.map(canonicalize).join(',') + ']'; } if (typeof obj === 'object') { const keys = Object.keys(obj).sort(); const pairs = []; for (const k of keys) { pairs.push(JSON.stringify(k) + ':' + canonicalize(obj[k])); } return '{' + pairs.join(',') + '}'; } return 'null'; } // Compute a content-addressable asset ID. // Excludes self-referential fields (asset_id itself) from the hash input. // Returns "sha256:". function computeAssetId(obj, excludeFields) { if (!obj || typeof obj !== 'object') return null; const exclude = new Set(Array.isArray(excludeFields) ? excludeFields : ['asset_id']); const clean = {}; for (const k of Object.keys(obj)) { if (exclude.has(k)) continue; clean[k] = obj[k]; } const canonical = canonicalize(clean); const hash = crypto.createHash('sha256').update(canonical, 'utf8').digest('hex'); return 'sha256:' + hash; } // Verify that an object's asset_id matches its content. function verifyAssetId(obj) { if (!obj || typeof obj !== 'object') return false; const claimed = obj.asset_id; if (!claimed || typeof claimed !== 'string') return false; const computed = computeAssetId(obj); return claimed === computed; } module.exports = { SCHEMA_VERSION, canonicalize, computeAssetId, verifyAssetId, };