Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

107 lines · 4.4 KBRaw History
1/**
2 * SHA-256 whose progress can be saved between requests. WebCrypto's digest can't be resumed,
3 * so a blob uploaded in several requests (the registry's chunked uploads) would otherwise be
4 * read back whole to hash it when the last request arrives.
5 */
6const K = new Uint32Array([
7 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
8 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
9 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
10 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
11]);
12const INIT = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
13
14export class Sha256 {
15 private h = new Uint32Array(INIT);
16 private w = new Uint32Array(64);
17 /** Bytes of an unfinished 64-byte block. */
18 private block = new Uint8Array(64);
19 private fill = 0;
20 private length = 0;
21
22 /** Continues from `save()`'s output. */
23 static restore(saved: string): Sha256 {
24 const s = JSON.parse(saved) as { h: number[]; n: number; b: string };
25 if (!Array.isArray(s.h) || s.h.length !== 8 || !Number.isSafeInteger(s.n) || s.n < 0) throw new Error("invalid SHA-256 state");
26 const x = new Sha256();
27 x.h.set(s.h);
28 x.length = s.n;
29 const b = Uint8Array.from(atob(s.b), (ch) => ch.charCodeAt(0));
30 if (b.length !== s.n % 64) throw new Error("invalid SHA-256 state");
31 x.block.set(b);
32 x.fill = b.length;
33 return x;
34 }
35
36 save(): string {
37 return JSON.stringify({ h: [...this.h], n: this.length, b: btoa(String.fromCharCode(...this.block.subarray(0, this.fill))) });
38 }
39
40 update(data: Uint8Array): this {
41 this.length += data.length;
42 let off = 0;
43 if (this.fill) {
44 const n = Math.min(64 - this.fill, data.length);
45 this.block.set(data.subarray(0, n), this.fill);
46 this.fill += n;
47 off = n;
48 if (this.fill < 64) return this;
49 this.compress(this.block, 0);
50 this.fill = 0;
51 }
52 for (; off + 64 <= data.length; off += 64) this.compress(data, off);
53 if (off < data.length) {
54 this.block.set(data.subarray(off), 0);
55 this.fill = data.length - off;
56 }
57 return this;
58 }
59
60 /** The digest as lowercase hex. The object can't be updated afterwards. */
61 hex(): string {
62 const bits = this.length * 8;
63 const pad = new Uint8Array((this.fill < 56 ? 56 : 120) - this.fill + 8);
64 pad[0] = 0x80;
65 const v = new DataView(pad.buffer);
66 v.setUint32(pad.length - 8, Math.floor(bits / 2 ** 32));
67 v.setUint32(pad.length - 4, bits >>> 0);
68 this.update(pad);
69 return [...this.h].map((x) => x.toString(16).padStart(8, "0")).join("");
70 }
71
72 private compress(p: Uint8Array, o: number): void {
73 const w = this.w;
74 for (let i = 0; i < 16; i++, o += 4) w[i] = (p[o]! << 24) | (p[o + 1]! << 16) | (p[o + 2]! << 8) | p[o + 3]!;
75 for (let i = 16; i < 64; i++) {
76 const a = w[i - 15]!;
77 const b = w[i - 2]!;
78 const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
79 const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
80 w[i] = (w[i - 16]! + s0 + w[i - 7]! + s1) | 0;
81 }
82 const h = this.h;
83 let a = h[0]!, b = h[1]!, c = h[2]!, d = h[3]!, e = h[4]!, f = h[5]!, g = h[6]!, hh = h[7]!;
84 for (let i = 0; i < 64; i++) {
85 const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
86 const t1 = (hh + S1 + ((e & f) ^ (~e & g)) + K[i]! + w[i]!) | 0;
87 const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
88 const t2 = (S0 + ((a & b) ^ (a & c) ^ (b & c))) | 0;
89 hh = g;
90 g = f;
91 f = e;
92 e = (d + t1) | 0;
93 d = c;
94 c = b;
95 b = a;
96 a = (t1 + t2) | 0;
97 }
98 h[0] = (h[0]! + a) | 0;
99 h[1] = (h[1]! + b) | 0;
100 h[2] = (h[2]! + c) | 0;
101 h[3] = (h[3]! + d) | 0;
102 h[4] = (h[4]! + e) | 0;
103 h[5] = (h[5]! + f) | 0;
104 h[6] = (h[6]! + g) | 0;
105 h[7] = (h[7]! + hh) | 0;
106 }
107}