poly1305.mjs raw

   1  // TinyJS Runtime — Poly1305 MAC (RFC 8439 §2.5)
   2  // Uses JS BigInt for 130-bit modular arithmetic.
   3  
   4  import { Slice } from './builtin.mjs';
   5  
   6  const P = (1n << 130n) - 5n; // 2^130 - 5
   7  
   8  function sliceToU8(s) {
   9    if (s instanceof Uint8Array) return s;
  10    if (typeof s === 'string') return new TextEncoder().encode(s);
  11    const u = new Uint8Array(s.$length);
  12    for (let i = 0; i < s.$length; i++) u[i] = s.$array[s.$offset + i];
  13    return u;
  14  }
  15  
  16  function u8ToSlice(u8) {
  17    const arr = new Array(u8.length);
  18    for (let i = 0; i < u8.length; i++) arr[i] = u8[i];
  19    return new Slice(arr, 0, u8.length, u8.length);
  20  }
  21  
  22  function le128(buf, off, len) {
  23    let n = 0n;
  24    for (let i = 0; i < len; i++) {
  25      n |= BigInt(buf[off + i]) << BigInt(i * 8);
  26    }
  27    return n;
  28  }
  29  
  30  function clamp(r) {
  31    // RFC 8439 §2.5.2: clamp r
  32    r &= 0x0ffffffc0ffffffc0ffffffc0fffffffn;
  33    return r;
  34  }
  35  
  36  function poly1305(key, data) {
  37    // r = clamp(key[0:16]), s = key[16:32]
  38    const r = clamp(le128(key, 0, 16));
  39    const s = le128(key, 16, 16);
  40  
  41    let acc = 0n;
  42    let off = 0;
  43    while (off < data.length) {
  44      const blockLen = Math.min(16, data.length - off);
  45      let n = le128(data, off, blockLen);
  46      // Add high bit: 2^(8*blockLen)
  47      n |= 1n << BigInt(blockLen * 8);
  48      acc = ((acc + n) * r) % P;
  49      off += blockLen;
  50    }
  51  
  52    // tag = (acc + s) mod 2^128
  53    const tag = (acc + s) & ((1n << 128n) - 1n);
  54  
  55    // Encode as 16-byte little-endian
  56    const out = new Uint8Array(16);
  57    let v = tag;
  58    for (let i = 0; i < 16; i++) {
  59      out[i] = Number(v & 0xffn);
  60      v >>= 8n;
  61    }
  62    return out;
  63  }
  64  
  65  export function MAC(key, data) {
  66    const k = sliceToU8(key);
  67    const d = sliceToU8(data);
  68    return u8ToSlice(poly1305(k, d));
  69  }
  70  
  71  export function Verify(key, data, tag) {
  72    const k = sliceToU8(key);
  73    const d = sliceToU8(data);
  74    const t = sliceToU8(tag);
  75    const computed = poly1305(k, d);
  76    if (computed.length !== t.length) return false;
  77    // Constant-time comparison
  78    let diff = 0;
  79    for (let i = 0; i < 16; i++) diff |= computed[i] ^ t[i];
  80    return diff === 0;
  81  }
  82