web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

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 };