~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
111 lines · 2.9 KB Raw History
/**
 * Incremental SHA-1, needed for pack trailers. Pure JS because WebCrypto's
 * digest() is one-shot and merged packs are built from multiple regions;
 * git still addresses packs with SHA-1 regardless of platform.
 */

export class Sha1 {
  constructor() {
    this.h0 = 0x67452301;
    this.h1 = 0xefcdab89;
    this.h2 = 0x98badcfe;
    this.h3 = 0x10325476;
    this.h4 = 0xc3d2e1f0;
    this.block = new Uint8Array(64);
    this.blockLen = 0;
    this.byteLength = 0;
    this.words = new Int32Array(80);
  }

  /**
   * @param {Uint8Array} bytes
   * @returns {this}
   */
  update(bytes) {
    this.byteLength += bytes.length;
    let offset = 0;
    while (offset < bytes.length) {
      const take = Math.min(64 - this.blockLen, bytes.length - offset);
      this.block.set(bytes.subarray(offset, offset + take), this.blockLen);
      this.blockLen += take;
      offset += take;
      if (this.blockLen === 64) {
        this.processBlock();
        this.blockLen = 0;
      }
    }
    return this;
  }

  processBlock() {
    const w = this.words;
    const b = this.block;
    for (let i = 0; i < 16; i++) {
      w[i] =
        (b[i * 4] << 24) |
        (b[i * 4 + 1] << 16) |
        (b[i * 4 + 2] << 8) |
        b[i * 4 + 3];
    }
    for (let i = 16; i < 80; i++) {
      const n = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16];
      w[i] = (n << 1) | (n >>> 31);
    }
    let a = this.h0;
    let bb = this.h1;
    let c = this.h2;
    let d = this.h3;
    let e = this.h4;
    for (let i = 0; i < 80; i++) {
      let f;
      let k;
      if (i < 20) {
        f = (bb & c) | (~bb & d);
        k = 0x5a827999;
      } else if (i < 40) {
        f = bb ^ c ^ d;
        k = 0x6ed9eba1;
      } else if (i < 60) {
        f = (bb & c) | (bb & d) | (c & d);
        k = 0x8f1bbcdc;
      } else {
        f = bb ^ c ^ d;
        k = 0xca62c1d6;
      }
      const temp = (((a << 5) | (a >>> 27)) + f + e + k + w[i]) | 0;
      e = d;
      d = c;
      c = (bb << 30) | (bb >>> 2);
      bb = a;
      a = temp;
    }
    this.h0 = (this.h0 + a) | 0;
    this.h1 = (this.h1 + bb) | 0;
    this.h2 = (this.h2 + c) | 0;
    this.h3 = (this.h3 + d) | 0;
    this.h4 = (this.h4 + e) | 0;
  }

  /** @returns {Uint8Array} 20-byte digest; the instance must not be reused */
  digest() {
    const bitLength = this.byteLength * 8;
    this.update(new Uint8Array([0x80]));
    while (this.blockLen !== 56) {
      this.update(new Uint8Array([0]));
    }
    const lengthBytes = new Uint8Array(8);
    new DataView(lengthBytes.buffer).setBigUint64(0, BigInt(bitLength));
    // Bypass update()'s length accounting for the final length field
    this.block.set(lengthBytes, 56);
    this.blockLen = 64;
    this.processBlock();
    const out = new Uint8Array(20);
    const view = new DataView(out.buffer);
    view.setInt32(0, this.h0);
    view.setInt32(4, this.h1);
    view.setInt32(8, this.h2);
    view.setInt32(12, this.h3);
    view.setInt32(16, this.h4);
    return out;
  }
}