chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
/**
* 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;
}
}