Skip to content

Instantly share code, notes, and snippets.

@kathawala
Created August 30, 2017 19:20
Show Gist options
  • Save kathawala/baa2626c8ae09101780fd8f73c1847cf to your computer and use it in GitHub Desktop.
Save kathawala/baa2626c8ae09101780fd8f73c1847cf to your computer and use it in GitHub Desktop.
Works fully, all rounds, all inputs
// requires "webgl.js" file for webgl utilities like "createProgramWithShaders"
// areas for speedup: using uniform buffers | replacing keccakf_rndc texture with
// a uniform that changes each round
"use strict";
const KECCAK_ROUNDS = 24;
var theta_chi = new Int8Array([ //RGBA8I
4, 1, 1, 2,
0, 2, 1, 2,
1, 3, 1, 2,
2, 4, 1, -3,
3, 0, -4, -3
]);
var rho_pi = new Int8Array([ // RGBA8I
// red is the index to rotate
// green is the number of bits to rotate
// blue is red % 5 [needed for indexing into theta_chi]
// alpha is floor(green / 16) [needed for calculating 16 bit rotation]
0, 0, 0, 0,
6, 44, 1, 2,
12, 43, 2, 2,
18, 21, 3, 1,
24, 14, 4, 0,
3, 28, 3, 1,
9, 20, 4, 1,
10, 3, 0, 0,
16, 45, 1, 2,
22, 61, 2, 3,
1, 1, 1, 0,
7, 6, 2, 0,
13, 25, 3, 1,
19, 8, 4, 0,
20, 18, 0, 1,
4, 27, 4, 1,
5, 36, 0, 2,
11, 10, 1, 0,
17, 15, 2, 0,
23, 56, 3, 3,
2, 62, 2, 3,
8, 55, 3, 3,
14, 39, 4, 2,
15, 41, 0, 2,
21, 2, 1, 0
]);
var keccakf_rndc = new Uint16Array([
1, 0, 0, 0,
32898, 0, 0, 0,
32906, 0, 0, 32768,
32768, 32768, 0, 32768,
32907, 0, 0, 0,
1, 32768, 0, 0,
32897, 32768, 0, 32768,
32777, 0, 0, 32768,
138, 0, 0, 0,
136, 0, 0, 0,
32777, 32768, 0, 0,
10, 32768, 0, 0,
32907, 32768, 0, 0,
139, 0, 0, 32768,
32905, 0, 0, 32768,
32771, 0, 0, 32768,
32770, 0, 0, 32768,
128, 0, 0, 32768,
32778, 0, 0, 0,
10, 32768, 0, 32768,
32897, 32768, 0, 32768,
32896, 0, 0, 32768,
1, 32768, 0, 0,
32776, 32768, 0, 32768,
]);
var vertexShaderSource = `#version 300 es
in vec2 a_position;
in vec2 a_textureCoordinates;
uniform vec2 u_resolution;
out vec2 v_textureCoordinates;
// all shaders have a main function
void main() {
// convert the position from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
// must set gl_Position in every vertex shader
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
v_textureCoordinates = a_textureCoordinates;
}
`;
var fragmentShaderSource = `#version 300 es
// fragment shaders don't have a default precision so we need
// to pick one. mediump is a good default. It means "medium precision"
precision highp float;
precision highp int;
precision highp usampler2D;
precision highp sampler2D;
precision highp isampler2D;
uniform usampler2D input_data;
uniform isampler2D theta_chi;
uniform isampler2D rho_pi;
uniform usampler2D keccakf_rndc;
uniform int round;
in vec2 v_textureCoordinates;
out uvec4 outColor;
// for rotations below 16 bits
uvec4 rotl_simple(uvec4 num, int shift) {
uint hihi = (num[3] << shift) | (num[2] >> (16-shift));
uint hilo = (num[2] << shift) | (num[1] >> (16-shift));
uint lohi = (num[1] << shift) | (num[0] >> (16-shift));
uint lolo = (num[0] << shift) | (num[3] >> (16-shift));
uvec4 result = uvec4(lolo, lohi, hilo, hihi);
// because shader is not aware of uint16 representation we & with 0xffff
result &= 65535u;
return result;
}
uvec4 rotl(uvec4 num, int shift, int movs) {
for(int i=0; i<movs; i++) {
num = uvec4(num[3], num[0], num[1], num[2]);
}
shift -= (movs*16);
return rotl_simple(num, shift);
}
uvec4 trp(int i) {
ivec4 rp_idxs = texelFetch(rho_pi, ivec2(i, 0), 0).rgba;
ivec2 theta_idxs = texelFetch(theta_chi, ivec2(rp_idxs.b, 0), 0).rg;
uvec4 xor = texelFetch(input_data, ivec2(theta_idxs.r, 0), 0) ^
texelFetch(input_data, ivec2(theta_idxs.r+5, 0), 0) ^
texelFetch(input_data, ivec2(theta_idxs.r+10, 0), 0) ^
texelFetch(input_data, ivec2(theta_idxs.r+15, 0), 0) ^
texelFetch(input_data, ivec2(theta_idxs.r+20, 0), 0);
uvec4 t = texelFetch(input_data, ivec2(theta_idxs.g, 0), 0) ^
texelFetch(input_data, ivec2(theta_idxs.g+5, 0), 0) ^
texelFetch(input_data, ivec2(theta_idxs.g+10, 0), 0) ^
texelFetch(input_data, ivec2(theta_idxs.g+15, 0), 0) ^
texelFetch(input_data, ivec2(theta_idxs.g+20, 0), 0);
uvec4 pre_rot = texelFetch(input_data, ivec2(rp_idxs.r, 0), 0) ^
(xor ^ rotl_simple(t, 1));
return rotl(pre_rot, rp_idxs.g, rp_idxs.a);
}
void main() {
// use i.x to store thread index (i.y is always 0)
// use thread index to lookup j (from rho pi) for the theta portion of lookups
// use thread index to lookup rho pi and chi portions.
int i = int(gl_FragCoord.x);
int s = i%5;
ivec2 chi_idxs = texelFetch(theta_chi, ivec2(s, 0), 0).ba;
uvec4 post_rho_pi = trp(i);
uvec4 prp1 = trp(i+chi_idxs[0]);
uvec4 prp2 = trp(i+chi_idxs[1]);
post_rho_pi ^= ((~prp1) & prp2);
if (i == 0) post_rho_pi ^= texelFetch(keccakf_rndc, ivec2(round, 0), 0);
outColor = post_rho_pi;
}
`;
function keccak(input, inlen) {
const HASH_DATA_AREA = 136;
var st = new Uint32Array(50);
// original code runs on 64-bit data, since JS can only handle 32
// we divide by 4 (bytes) instead of 8 (bytes) as in original code
var rsizw = HASH_DATA_AREA/4;
// for loop which gets skipped on small inputs!!!
var end = 0;
var special_epochs = Math.floor(inlen / HASH_DATA_AREA);
if (special_epochs > 0) {
inlen = inlen % HASH_DATA_AREA;
for (var j = 0; j < special_epochs; ++j) {
var start = j*HASH_DATA_AREA;
end = start+HASH_DATA_AREA;
var temp = new Uint8Array(144);
temp.set(input.subarray(start, start+HASH_DATA_AREA));
var ttemp = new Uint32Array(temp.buffer);
for (i=0; i<rsizw; ++i) {
st[i] ^= ttemp[i];
}
st = keccakf(st);
}
}
var temp = new Uint8Array(144);
temp.set(input.subarray(end,));
temp[inlen++] = 1;
temp[HASH_DATA_AREA-1] |= 0x80;
var ttemp = new Uint32Array(temp.buffer);
for (i=0; i<rsizw; ++i) {
st[i] ^= ttemp[i];
}
st = keccakf(st);
return st.buffer;
}
function keccakf(input) {
// https://webgl2fundamentals.org/webgl/lessons/webgl-image-processing-continued.html
// and use twgl for setting uniforms, attributes, programInfo, etc. Seems good.
var gl = setupWebGLFromCanvas(document.getElementById('c')); // util func
var ext = gl.getExtension('EXT_color_buffer_float');
if (!ext) console.log("ERROR EXTENSION");
var program = createProgramWithShaders(gl, vertexShaderSource, fragmentShaderSource);
var vertexArrayObject = getVertexArrayObject(gl);
var textureVertices = new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0
]);
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
var textureCoordinatesAttributeLocation = gl.getAttribLocation(program, "a_textureCoordinates");
setupVerticesAndEnableAttribArray(gl, textureVertices, textureCoordinatesAttributeLocation, size, type, normalize, stride, offset);
var inputTexture = createAndSetupMultiTexture(gl, 0);
var mipLevel = 0; // the largest mip
var internalFormat = gl.RGBA16UI; // format we want in the texture
var srcFormat = gl.RGBA_INTEGER; // format of data we are supplying
var srcType = gl.UNSIGNED_SHORT; // type of data we are supplying
var width = 25;
var height = 1;
var border = 0;
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
var sixteen_input = new Uint16Array(input.buffer);
gl.texImage2D(gl.TEXTURE_2D, mipLevel, internalFormat, width, height, border, srcFormat, srcType, sixteen_input);
var thetaChiTexture = createAndSetupMultiTexture(gl, 1);
internalFormat = gl.RGBA8I;
srcFormat = gl.RGBA_INTEGER;
srcType = gl.BYTE;
width = 5;
gl.texImage2D(gl.TEXTURE_2D, mipLevel, internalFormat, width, height, border, srcFormat, srcType, theta_chi);
var rhoPiTexture = createAndSetupMultiTexture(gl, 2);
internalFormat = gl.RGBA8I;
srcFormat = gl.RGBA_INTEGER;
srcType = gl.BYTE
width = 25;
gl.texImage2D(gl.TEXTURE_2D, mipLevel, internalFormat, width, height, border, srcFormat, srcType, rho_pi);
// best to make this a uniform which changes every round of array drawing
var keccakfRndcTexture = createAndSetupMultiTexture(gl, 3);
internalFormat = gl.RGBA16UI;
srcFormat = gl.RGBA_INTEGER;
srcType = gl.UNSIGNED_SHORT;
width = 24;
gl.texImage2D(gl.TEXTURE_2D, mipLevel, internalFormat, width, height, border, srcFormat, srcType, keccakf_rndc);
setupCanvas(gl);
var outputTexture = createAndSetupMultiTexture(gl, 4);
internalFormat = gl.RGBA16UI;
srcFormat = gl.RGBA_INTEGER;
srcType = gl.UNSIGNED_SHORT;
width = 25;
gl.texImage2D(gl.TEXTURE_2D, mipLevel, internalFormat, width, height, border, srcFormat, srcType, null);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Bind the attribute/buffer set we want.
gl.bindVertexArray(vertexArrayObject);
// play with moving this block around
var inputLocation = gl.getUniformLocation(program, "input_data");
var thetaChiLocation = gl.getUniformLocation(program, "theta_chi");
var rhoPiLocation = gl.getUniformLocation(program, "rho_pi");
var keccakfRndcLocation = gl.getUniformLocation(program, "keccakf_rndc");
gl.uniform1i(inputLocation, 0);
gl.uniform1i(thetaChiLocation, 1);
gl.uniform1i(rhoPiLocation, 2);
gl.uniform1i(keccakfRndcLocation, 3);
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
var vertices = createVertexArrayFromImage(width, height);
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
setupVerticesAndEnableAttribArray(gl, vertices, positionAttributeLocation, size, type, normalize, stride, offset);
// draw
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
// framebuffer for ping-ponging
var framebuffer = gl.createFramebuffer();
var attachmentPoint = gl.COLOR_ATTACHMENT0;
for(var i=0; i<KECCAK_ROUNDS; i++) { // make a better construct for this
if (i%2==1){
bindTextures(gl, [outputTexture, thetaChiTexture, rhoPiTexture, keccakfRndcTexture]);
bindTextureToFramebuffer(gl, inputTexture, attachmentPoint, framebuffer);
} else {
bindTextures(gl, [inputTexture, thetaChiTexture, rhoPiTexture, keccakfRndcTexture]);
bindTextureToFramebuffer(gl, outputTexture, attachmentPoint, framebuffer);
}
var roundLocation = gl.getUniformLocation(program, "round");
gl.uniform1i(roundLocation, i);
gl.drawArrays(primitiveType, offset, count);
}
return readRenderedOutput(gl, width, height);
}
function createVertexArrayFromImage(width, height) {
var x1 = 0;
var x2 = width;
var y1 = 0;
var y2 = height;
return new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2
]);
}
function readRenderedOutput(gl, width, height) {
// 4 because the fragment shader declares output colors as vec4 (aka RGBA)
var output_buffer = new ArrayBuffer(gl.drawingBufferWidth * gl.drawingBufferHeight * 4 * 4);
var output_view = new Uint32Array(output_buffer);
switch(gl.checkFramebufferStatus(gl.FRAMEBUFFER)) {
case gl.FRAMEBUFFER_COMPLETE:
gl.readBuffer(gl.COLOR_ATTACHMENT0); // WARNING: CHANGE THIS!!!!
gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA_INTEGER, gl.UNSIGNED_INT, output_view);
var conversion = new Uint16Array(output_view);
var output = new Uint32Array(conversion.buffer);
return output;
break;
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
console.log("ERROR: FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
break;
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
console.log("ERROR: FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
break;
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
console.log("ERROR: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
break;
case gl.FRAMEBUFFER_UNSUPPORTED:
console.log("ERROR: FRAMEBUFFER_UNSUPPORTED");
break;
}
return null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment