/** * OCI digest and blob CID conversion. Both name the same SHA-256 hash: a * digest as `sha256:`, a blob CID as CIDv1 + raw codec + sha256 * multihash. Converting between them is re-encoding, not a lookup. */ import { blobCidFromDigest, cidToBytes, cidToString } from '@pdsjs/core/repo'; const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/; /** * @param {string} digest * @returns {boolean} */ export function isValidDigest(digest) { return DIGEST_PATTERN.test(digest); } /** * @param {string} hex - 64 lowercase hex characters * @returns {Uint8Array} */ function hexToBytes(hex) { const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16); } return bytes; } /** * @param {Uint8Array} bytes * @returns {string} */ export function bytesToHex(bytes) { let hex = ''; for (const byte of bytes) { hex += byte.toString(16).padStart(2, '0'); } return hex; } /** * @param {string} digest - `sha256:` * @returns {string} base32lower blob CID string */ export function digestToCid(digest) { if (!isValidDigest(digest)) { throw new Error(`unsupported digest: ${digest}`); } return cidToString(blobCidFromDigest(hexToBytes(digest.slice(7)))); } /** * @param {string} cid - base32lower blob CID string * @returns {string} `sha256:` */ export function cidToDigest(cid) { const bytes = cidToBytes(cid); // CIDv1 + codec + multihash prefix take the first four bytes; the sha256 // digest is the 32 that follow. if (bytes.length !== 36 || bytes[2] !== 0x12 || bytes[3] !== 0x20) { throw new Error(`not a sha256 CID: ${cid}`); } return `sha256:${bytesToHex(bytes.subarray(4))}`; } /** * @param {Uint8Array} hashBytes - 32-byte SHA-256 digest * @returns {string} `sha256:` */ export function digestFromHashBytes(hashBytes) { return `sha256:${bytesToHex(hashBytes)}`; }