// Byte plumbing for the private path. Isomorphic by construction: no `node:buffer`, no `atob`, no // `Buffer` — the browser tab and the daemon have to produce identical bytes or they produce // different CIDs, and the surest way to guarantee that is for both to run this. const BASE32_LOWER = 'abcdefghijklmnopqrstuvwxyz234567' const BASE64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' const ENCODER = new TextEncoder() const DECODER = new TextDecoder() export const utf8 = (value: string): Uint8Array => ENCODER.encode(value) export const fromUtf8 = (bytes: Uint8Array): string => DECODER.decode(bytes) export function concat(...parts: Uint8Array[]): Uint8Array { const total = parts.reduce((sum, part) => sum + part.length, 0) const out = new Uint8Array(total) let offset = 0 for (const part of parts) { out.set(part, offset) offset += part.length } return out } export function equalBytes(left: Uint8Array, right: Uint8Array): boolean { if (left.length !== right.length) return false for (let index = 0; index < left.length; index += 1) { if (left[index] !== right[index]) return false } return true } /** Bytewise comparison — the tie-break inside DAG-CBOR's length-first map key ordering. */ export function compareBytes(left: Uint8Array, right: Uint8Array): number { const shared = Math.min(left.length, right.length) for (let index = 0; index < shared; index += 1) { const a = left[index] as number const b = right[index] as number if (a !== b) return a < b ? -1 : 1 } return left.length - right.length } /** RFC 4648 base32, lowercase, unpadded — the multibase `b` alphabet CIDv1 is written in. */ export function base32Encode(bytes: Uint8Array): string { let out = '' let buffer = 0 let bits = 0 for (const byte of bytes) { buffer = (buffer << 8) | byte bits += 8 while (bits >= 5) { out += BASE32_LOWER[(buffer >>> (bits - 5)) & 31] bits -= 5 } } if (bits > 0) out += BASE32_LOWER[(buffer << (5 - bits)) & 31] return out } export function base32Decode(value: string): Uint8Array { const out: number[] = [] let buffer = 0 let bits = 0 for (const character of value) { const index = BASE32_LOWER.indexOf(character) if (index === -1) throw new TypeError(`not base32: ${value}`) buffer = (buffer << 5) | index bits += 5 if (bits >= 8) { out.push((buffer >>> (bits - 8)) & 0xff) bits -= 8 } } return new Uint8Array(out) } /** Unpadded base64url — how a public key and a signature travel in a record or an envelope. */ export function base64urlEncode(bytes: Uint8Array): string { let out = '' for (let index = 0; index < bytes.length; index += 3) { const a = bytes[index] as number const b = bytes[index + 1] const c = bytes[index + 2] out += BASE64URL[a >>> 2] out += BASE64URL[((a & 3) << 4) | ((b ?? 0) >>> 4)] if (b === undefined) break out += BASE64URL[((b & 15) << 2) | ((c ?? 0) >>> 6)] if (c === undefined) break out += BASE64URL[c & 63] } return out } export function base64urlDecode(value: string): Uint8Array { const out: number[] = [] let buffer = 0 let bits = 0 for (const character of value) { const index = BASE64URL.indexOf(character) if (index === -1) throw new TypeError(`not base64url: ${value}`) buffer = (buffer << 6) | index bits += 6 if (bits >= 8) { out.push((buffer >>> (bits - 8)) & 0xff) bits -= 8 } } return new Uint8Array(out) } export function hexEncode(bytes: Uint8Array): string { let out = '' for (const byte of bytes) out += byte.toString(16).padStart(2, '0') return out }