mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 16:07:12 +00:00
Since releases ship an `.apk.sha256` file, every in-app update checks the downloaded APK against it. The check read the 40 MB file in base64 chunks and hashed it in JS, which took longer than the download, and it reset the progress bar to 0 under the "Downloading" label, so the update looked like it downloaded twice, the second time slowly. The file is now read and hashed natively through expo-crypto (the JS hasher remains as a fallback), and the bar stays full under a "Verifying…" label while the check runs.
131 lines
4.4 KiB
TypeScript
131 lines
4.4 KiB
TypeScript
// Minimal SHA-256 implementation. The fallback for verifying APK update
|
|
// downloads when expo-crypto's native digest is unavailable (see
|
|
// `hashFile` in install-update.ts); far too slow to be the main path.
|
|
// Public-domain algorithm (FIPS 180-4).
|
|
|
|
const K = new Uint32Array([
|
|
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
|
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
|
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
|
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
|
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
|
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
|
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
|
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
]);
|
|
|
|
function rotr(x: number, n: number): number {
|
|
return (x >>> n) | (x << (32 - n));
|
|
}
|
|
|
|
export class Sha256 {
|
|
private h = new Uint32Array([
|
|
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
]);
|
|
private buffer = new Uint8Array(64);
|
|
private bufferLen = 0;
|
|
private byteCount = 0;
|
|
|
|
update(bytes: Uint8Array): void {
|
|
let i = 0;
|
|
this.byteCount += bytes.length;
|
|
while (i < bytes.length) {
|
|
const room = 64 - this.bufferLen;
|
|
const chunk = Math.min(room, bytes.length - i);
|
|
this.buffer.set(bytes.subarray(i, i + chunk), this.bufferLen);
|
|
this.bufferLen += chunk;
|
|
i += chunk;
|
|
if (this.bufferLen === 64) {
|
|
this.process(this.buffer);
|
|
this.bufferLen = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
digestHex(): string {
|
|
// Final block: append 0x80, pad with zeros, then 8-byte big-endian length.
|
|
const totalBits = this.byteCount * 8;
|
|
this.buffer[this.bufferLen++] = 0x80;
|
|
if (this.bufferLen > 56) {
|
|
while (this.bufferLen < 64) this.buffer[this.bufferLen++] = 0;
|
|
this.process(this.buffer);
|
|
this.bufferLen = 0;
|
|
}
|
|
while (this.bufferLen < 56) this.buffer[this.bufferLen++] = 0;
|
|
// 64-bit length, big-endian. We can't represent >2^53 - JS bitops are
|
|
// 32-bit, so split into hi/lo words.
|
|
const hi = Math.floor(totalBits / 0x100000000);
|
|
const lo = totalBits >>> 0;
|
|
this.buffer[56] = (hi >>> 24) & 0xff;
|
|
this.buffer[57] = (hi >>> 16) & 0xff;
|
|
this.buffer[58] = (hi >>> 8) & 0xff;
|
|
this.buffer[59] = hi & 0xff;
|
|
this.buffer[60] = (lo >>> 24) & 0xff;
|
|
this.buffer[61] = (lo >>> 16) & 0xff;
|
|
this.buffer[62] = (lo >>> 8) & 0xff;
|
|
this.buffer[63] = lo & 0xff;
|
|
this.process(this.buffer);
|
|
|
|
let out = '';
|
|
for (let i = 0; i < 8; i++) {
|
|
out += this.h[i].toString(16).padStart(8, '0');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
private process(block: Uint8Array): void {
|
|
const w = new Uint32Array(64);
|
|
for (let i = 0; i < 16; i++) {
|
|
w[i] =
|
|
(block[i * 4] << 24) |
|
|
(block[i * 4 + 1] << 16) |
|
|
(block[i * 4 + 2] << 8) |
|
|
block[i * 4 + 3];
|
|
}
|
|
for (let i = 16; i < 64; i++) {
|
|
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
|
|
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
|
|
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0;
|
|
}
|
|
|
|
let a = this.h[0], b = this.h[1], c = this.h[2], d = this.h[3];
|
|
let e = this.h[4], f = this.h[5], g = this.h[6], hh = this.h[7];
|
|
|
|
for (let i = 0; i < 64; i++) {
|
|
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
const ch = (e & f) ^ (~e & g);
|
|
const temp1 = (hh + S1 + ch + K[i] + w[i]) | 0;
|
|
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
const mj = (a & b) ^ (a & c) ^ (b & c);
|
|
const temp2 = (S0 + mj) | 0;
|
|
hh = g;
|
|
g = f;
|
|
f = e;
|
|
e = (d + temp1) | 0;
|
|
d = c;
|
|
c = b;
|
|
b = a;
|
|
a = (temp1 + temp2) | 0;
|
|
}
|
|
|
|
this.h[0] = (this.h[0] + a) | 0;
|
|
this.h[1] = (this.h[1] + b) | 0;
|
|
this.h[2] = (this.h[2] + c) | 0;
|
|
this.h[3] = (this.h[3] + d) | 0;
|
|
this.h[4] = (this.h[4] + e) | 0;
|
|
this.h[5] = (this.h[5] + f) | 0;
|
|
this.h[6] = (this.h[6] + g) | 0;
|
|
this.h[7] = (this.h[7] + hh) | 0;
|
|
}
|
|
}
|
|
|
|
export function sha256Hex(bytes: Uint8Array): string {
|
|
const h = new Sha256();
|
|
h.update(bytes);
|
|
return h.digestHex();
|
|
}
|