SHA-256 is one of the most commonly used cryptographic hash functions.
Useful links
- https://www.youtube.com/watch?v=f9EbD6iY9zI
- https://sha256algorithm.com/
Typescript implementation
I wrote this for a Nostr client back in December 2022.
// This file implements the SHA-256 hash function.
// See https://en.wikipedia.org/wiki/SHA-2#Pseudocode
// Everything is done in big-endian.
const K = [
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 u32(x: number): number {
return (x & 0xffffffff) >>> 0;
}
function rotate_right(x: number, n: number): number {
return u32((x >>> n) | (x << (32 - n)));
}
function pad_to_512_bits(message: Uint8Array): Uint8Array {
const len = BigInt(message.length);
const bits = BigInt(len * 8n);
const k = 512n - ((bits + 1n + 64n) % 512n);
const padded = new Uint8Array(Number(len + 1n + k / 8n + 8n));
padded.set(message);
padded[Number(len)] = 0x80;
padded[padded.length - 8] = Number(bits >> 56n);
padded[padded.length - 7] = Number(bits >> 48n);
padded[padded.length - 6] = Number(bits >> 40n);
padded[padded.length - 5] = Number(bits >> 32n);
padded[padded.length - 4] = Number(bits >> 24n);
padded[padded.length - 3] = Number(bits >> 16n);
padded[padded.length - 2] = Number(bits >> 8n);
padded[padded.length - 1] = Number(bits);
assert(padded.length % 64 === 0);
return padded;
}
function sha256(message: Uint8Array): Uint8Array {
const padded = pad_to_512_bits(message);
let h0 = 0x6a09e667;
let h1 = 0xbb67ae85;
let h2 = 0x3c6ef372;
let h3 = 0xa54ff53a;
let h4 = 0x510e527f;
let h5 = 0x9b05688c;
let h6 = 0x1f83d9ab;
let h7 = 0x5be0cd19;
for (let chunk_start = 0; chunk_start < padded.length; chunk_start += 64) {
const chunk = padded.slice(chunk_start, chunk_start + 64);
const data = new DataView(chunk.buffer);
const w = new Array<number>(64);
for (let i = 0; i < 16; i++) {
w[i] = data.getUint32(i * 4, false);
}
for (let i = 16; i < 64; i++) {
const s0 =
rotate_right(w[i - 15], 7) ^
rotate_right(w[i - 15], 18) ^
(w[i - 15] >>> 3);
const s1 =
rotate_right(w[i - 2], 17) ^
rotate_right(w[i - 2], 19) ^
(w[i - 2] >>> 10);
w[i] = u32(w[i - 16] + s0 + w[i - 7] + s1);
}
// Working variables
let a = h0;
let b = h1;
let c = h2;
let d = h3;
let e = h4;
let f = h5;
let g = h6;
let h = h7;
// Compression function main loop
for (let i = 0; i < 64; i++) {
const S1 = rotate_right(e, 6) ^ rotate_right(e, 11) ^ rotate_right(e, 25);
const ch = (e & f) ^ (~e & g);
const temp1 = u32(h + S1 + ch + K[i] + w[i]);
const S0 = rotate_right(a, 2) ^ rotate_right(a, 13) ^ rotate_right(a, 22);
const maj = (a & b) ^ (a & c) ^ (b & c);
const temp2 = u32(S0 + maj);
h = g;
g = f;
f = e;
e = u32(d + temp1);
d = c;
c = b;
b = a;
a = u32(temp1 + temp2);
}
// Add the compressed chunk to the current hash value
h0 = u32(h0 + a);
h1 = u32(h1 + b);
h2 = u32(h2 + c);
h3 = u32(h3 + d);
h4 = u32(h4 + e);
h5 = u32(h5 + f);
h6 = u32(h6 + g);
h7 = u32(h7 + h);
}
// Produce the final hash value (big-endian)
const hash = new Uint8Array(32);
const hash_data = new DataView(hash.buffer);
hash_data.setUint32(0, h0, false);
hash_data.setUint32(4, h1, false);
hash_data.setUint32(8, h2, false);
hash_data.setUint32(12, h3, false);
hash_data.setUint32(16, h4, false);
hash_data.setUint32(20, h5, false);
hash_data.setUint32(24, h6, false);
hash_data.setUint32(28, h7, false);
return hash;
}
export default sha256;