envelope.ts (2844B)
1 import { cl_crypto_error } from "./error.js"; 2 import type { CryptoEnvelope } from "./types.js"; 3 4 const ENVELOPE_MAGIC = new Uint8Array([0x52, 0x52, 0x43, 0x45]); 5 const ENVELOPE_VERSION = 1; 6 const ENVELOPE_HEADER_LENGTH = 4 + 1 + 1 + 1 + 8; 7 const TEXT_ENCODER = new TextEncoder(); 8 const TEXT_DECODER = new TextDecoder(); 9 10 const bytes_equal = (left: Uint8Array, right: Uint8Array): boolean => { 11 if (left.length !== right.length) return false; 12 for (let i = 0; i < left.length; i++) if (left[i] !== right[i]) return false; 13 return true; 14 }; 15 16 export const crypto_envelope_encode = (envelope: CryptoEnvelope): Uint8Array => { 17 const key_bytes = TEXT_ENCODER.encode(envelope.key_id); 18 if (key_bytes.length > 255) throw new Error(cl_crypto_error.invalid_key_id); 19 const total_len = ENVELOPE_HEADER_LENGTH + key_bytes.length + envelope.iv.length + envelope.ciphertext.length; 20 const out = new Uint8Array(total_len); 21 const view = new DataView(out.buffer, out.byteOffset, out.byteLength); 22 let offset = 0; 23 out.set(ENVELOPE_MAGIC, offset); 24 offset += ENVELOPE_MAGIC.length; 25 out[offset] = ENVELOPE_VERSION; 26 offset += 1; 27 out[offset] = key_bytes.length; 28 offset += 1; 29 out[offset] = envelope.iv.length; 30 offset += 1; 31 view.setBigUint64(offset, BigInt(envelope.created_at), false); 32 offset += 8; 33 out.set(key_bytes, offset); 34 offset += key_bytes.length; 35 out.set(envelope.iv, offset); 36 offset += envelope.iv.length; 37 out.set(envelope.ciphertext, offset); 38 return out; 39 }; 40 41 export const crypto_envelope_decode = (blob: Uint8Array): CryptoEnvelope | null => { 42 if (blob.byteLength < ENVELOPE_HEADER_LENGTH) return null; 43 const magic = blob.subarray(0, ENVELOPE_MAGIC.length); 44 if (!bytes_equal(magic, ENVELOPE_MAGIC)) return null; 45 const view = new DataView(blob.buffer, blob.byteOffset, blob.byteLength); 46 let offset = ENVELOPE_MAGIC.length; 47 const version = view.getUint8(offset); 48 offset += 1; 49 if (version !== ENVELOPE_VERSION) throw new Error(cl_crypto_error.invalid_envelope); 50 const key_len = view.getUint8(offset); 51 offset += 1; 52 const iv_len = view.getUint8(offset); 53 offset += 1; 54 const created_at = Number(view.getBigUint64(offset, false)); 55 offset += 8; 56 const remaining = blob.byteLength - offset; 57 if (remaining < key_len + iv_len + 1) throw new Error(cl_crypto_error.invalid_envelope); 58 const key_bytes = blob.subarray(offset, offset + key_len); 59 offset += key_len; 60 const iv = blob.subarray(offset, offset + iv_len); 61 offset += iv_len; 62 const ciphertext = blob.subarray(offset); 63 const key_id = TEXT_DECODER.decode(key_bytes); 64 if (!key_id) throw new Error(cl_crypto_error.invalid_key_id); 65 return { 66 version, 67 key_id, 68 iv, 69 created_at, 70 ciphertext 71 }; 72 };