diff options
author | Runxi Yu <me@runxiyu.org> | 2025-03-24 01:30:53 +0800 |
---|---|---|
committer | Runxi Yu <me@runxiyu.org> | 2025-03-24 20:39:10 +0800 |
commit | 13c4a755e66cbff0ac0c65eece015bddbe152ed2 (patch) | |
tree | 8924deb64637ced0753a7ad341bf968af2c5f94e | |
parent | tmpl.go: Separate the template into its own file (diff) | |
download | powxy-13c4a755e66cbff0ac0c65eece015bddbe152ed2.tar.gz powxy-13c4a755e66cbff0ac0c65eece015bddbe152ed2.tar.zst powxy-13c4a755e66cbff0ac0c65eece015bddbe152ed2.zip |
Use a WebAssembly solverv0.1.10
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | challenge.html (renamed from challenge.tmpl) | 218 | ||||
-rw-r--r-- | csolver.go | 183 | ||||
-rw-r--r-- | flags.go | 2 | ||||
-rw-r--r-- | global.go | 4 | ||||
-rw-r--r-- | main.go | 5 | ||||
-rw-r--r-- | resources.go | 12 | ||||
-rw-r--r-- | static/solver.c | 182 | ||||
-rw-r--r-- | static/solver.js | 34 | ||||
-rw-r--r-- | static/style.css | 121 | ||||
-rw-r--r-- | tmpl.go | 2 | ||||
-rw-r--r-- | wasm/.gitignore | 1 | ||||
-rw-r--r-- | wasm/sha256.c | 163 | ||||
-rw-r--r-- | wasm/sha256.h | 34 | ||||
-rw-r--r-- | wasm/solver.c | 51 |
17 files changed, 638 insertions, 382 deletions
@@ -1,2 +1,3 @@ /version.go /powxy +*.wasm @@ -1,7 +1,10 @@ -powxy: *.go version.go +powxy: *.go version.go wasm/solver.wasm go build -o powxy version.go: printf 'package main\n\nfunc init() {\n\tglobal.Version = "%s"\n}\n' $(shell git describe --tags --always --dirty) > $@ +wasm/solver.wasm: wasm/solver.c wasm/sha256.c wasm/sha256.h + clang --target=wasm32 -nostdlib -Wl,--no-entry -Wl,--export-all -o wasm/solver.wasm wasm/solver.c wasm/sha256.c + .PHONY: version.go @@ -32,7 +32,7 @@ It may experience occasional outages. ## Build -You need a working Go installation. +You need a working Go installation, along with Clang and LLD for WebAssembly. ``` git clone ssh://forge.lindenii.runxiyu.org/powxy/:/repos/powxy/ diff --git a/challenge.tmpl b/challenge.html index 13eac3d..ea18794 100644 --- a/challenge.tmpl +++ b/challenge.html @@ -4,129 +4,7 @@ <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Proof-of-work challenge</title> - <style> - html { - font-family: sans-serif; - background-color: var(--background-color); - color: var(--text-color); - --radius-1: 0.32rem; - --background-color: hsl(0, 0%, 100%); - --text-color: hsl(0, 0%, 0%); - --link-color: hsl(320, 50%, 36%); - --light-text-color: hsl(0, 0%, 45%); - --darker-border-color: hsl(0, 0%, 72%); - --lighter-border-color: hsl(0, 0%, 85%); - --text-decoration-color: hsl(0, 0%, 72%); - --darker-box-background-color: hsl(0, 0%, 92%); - --lighter-box-background-color: hsl(0, 0%, 95%); - --primary-color: hsl(320, 50%, 36%); - --primary-color-contrast: hsl(320, 0%, 100%); - --danger-color: #ff0000; - --danger-color-contrast: #ffffff; - } - - @media (prefers-color-scheme: dark) { - html { - --background-color: hsl(0, 0%, 0%); - --text-color: hsl(0, 0%, 100%); - --link-color: hsl(320, 50%, 76%); - --light-text-color: hsl(0, 0%, 78%); - --darker-border-color: hsl(0, 0%, 35%); - --lighter-border-color: hsl(0, 0%, 25%); - --text-decoration-color: hsl(0, 0%, 30%); - --darker-box-background-color: hsl(0, 0%, 20%); - --lighter-box-background-color: hsl(0, 0%, 15%); - } - } - - body { - margin: 0; - padding: 1rem; - } - - main { - max-width: 720px; - margin: 0 auto; - } - - *:focus-visible { - outline: 1.5px var(--primary-color) solid; - } - - section { - margin: 0; - } - - label { - display: block; - font-style: italic; - margin-top: 1rem; - margin-bottom: 0.5rem; - } - - h1 { - margin-top: 0; - } - - p, summary { - line-height: 1.2; - font-size: 1rem; - } - - a { - color: var(--link-color); - text-decoration-color: var(--text-decoration-color); - } - - input[type="text"] { - font-family: monospace; - font-size: 1rem; - background-color: var(--lighter-box-background-color); - color: var(--text-color); - width: 100%; - padding: 0.5rem; - border-radius: var(--radius-1); - border: none; - box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.15); - margin-bottom: 1rem; - box-sizing: border-box; - } - - input[type="submit"] { - padding: 0.5rem 1rem; - background-color: var(--primary-color); - color: var(--primary-color-contrast); - border: none; - border-radius: var(--radius-1); - cursor: pointer; - } - - input[readonly] { - background-color: var(--lighter-box-background-color); - color: var(--text-color); - cursor: text; - } - - details { - margin-top: 2rem; - background-color: var(--lighter-box-background-color); - padding: 0.5rem; - border-radius: var(--radius-1); - box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.15); - } - - pre { - overflow-x: auto; - display: block; - white-space: pre-wrap; - word-break: break-word; - } - - #solver_status { - color: var(--light-text-color); - margin-top: 1rem; - } - </style> + <link rel="stylesheet" href="/.powxy/static/style.css" /> <script> /* @licstart The following is the entire license notice for the @@ -200,86 +78,36 @@ <p id="solver_status">JavaScript seems to be disabled. You must solve the challenge externally.</p> </section> - <details> - <summary>Offline solver program</summary> - <pre>` + html.EscapeString(solverProgram) + `</pre> - </details> + <section> + <p><a href="/.powxy/static/solver.c">A C implementation of the challenge solver</a> is available.</p> + </section> </main> <script> - document.addEventListener("DOMContentLoaded", function() { + document.addEventListener("DOMContentLoaded", async function() { let challenge_b64 = "{{ .Identifier }}"; let difficulty = {{ .Global.NeedBits }}; let form = document.querySelector("form"); let field = form.querySelector("input[name='powxy']"); let status_el = document.getElementById("solver_status"); - - if (!window.crypto || !window.crypto.subtle) { - status_el.textContent = "SubtleCrypto not available. You must solve the challenge externally."; - return; - } - status_el.textContent = "SubtleCrypto detected. Attempting to solve the challenge automatically..."; - - let solver_active = true; - form.addEventListener("submit", function() { - solver_active = false; - }); - - async function solve_pow() { - let identifier_bytes = Uint8Array.from( - atob(challenge_b64), - ch => ch.charCodeAt(0) - ); - - let nonce = 0n; - let buf = new ArrayBuffer(8); - let view = new DataView(buf); - - while (solver_active) { - view.setBigUint64(0, nonce, true); - - let candidate = new Uint8Array(identifier_bytes.length + 8); - candidate.set(identifier_bytes, 0); - candidate.set(new Uint8Array(buf), identifier_bytes.length); - - let digest_buffer = await crypto.subtle.digest("SHA-256", candidate); - let digest = new Uint8Array(digest_buffer); - - if (has_leading_zero_bits(digest, difficulty)) { - let nonce_str = String.fromCharCode(...new Uint8Array(buf)); - field.value = btoa(nonce_str); - - status_el.textContent = "A solution has been found automatically in " + nonce + " iterations."; - return; - } - - nonce++; - - if ((nonce & 0x00FFn) === 0n) { - status_el.textContent = "Attempting to solve automatically. Tried " + nonce + " candidates so far..."; - await new Promise(r => setTimeout(r, 0)); - } - } - } - - function has_leading_zero_bits(digest, bits) { - let full_bytes = bits >>> 3; - for (let i = 0; i < full_bytes; i++) { - if (digest[i] !== 0) { - return false; - } - } - let remainder = bits & 7; - if (remainder !== 0) { - let mask = 0xFF << (8 - remainder); - if ((digest[full_bytes] & mask) !== 0) { - return false; - } - } - return true; - } - - solve_pow(); + + let identifier_bytes = Uint8Array.from( + atob(challenge_b64), + ch => ch.charCodeAt(0) + ); + + status_el.textContent = "Starting WebAssembly solver as a worker..."; + + let worker = new Worker("/.powxy/static/solver.js"); + + worker.onmessage = function(e) { + let { nonce, nonce_bytes } = e.data; + let nonce_str = String.fromCharCode(...nonce_bytes); + field.value = btoa(nonce_str); + status_el.textContent = "Challenge solved automatically in " + nonce + " iterations"; + }; + + worker.postMessage({ identifier_bytes, difficulty }); }); </script> </body> @@ -3,185 +3,4 @@ package main -const solverProgram = `// This is a reference implementation of the proof of work solver in C. -// For security reasons, it is recommended that you read and understand the -// entire program first if you actually want to run it. -// -// You need to have OpenSSL, and link with -lcrypto - -#include <openssl/evp.h> -#include <openssl/bio.h> -#include <openssl/buffer.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include <stdlib.h> -#include <errno.h> - -bool validate_bit_zeros(const unsigned char *bs, unsigned long n) -{ - unsigned long q = n / CHAR_BIT; - unsigned char r = n % CHAR_BIT; - - for (unsigned long i = 0; i < q; i++) { - if (bs[i] != 0) - return false; - } - - if (r > 0) { - unsigned char mask = - (unsigned char)(UCHAR_MAX << (CHAR_BIT - r)); - if (bs[q] & mask) - return false; - } - - return true; -} - -int main(int argc, char **argv) -{ - if (argc < 3) { - fprintf(stderr, "usage: %s <base64_data> <difficulty>\n", - argv[0]); - return 1; - } - - size_t base64_data_len = strlen(argv[1]); - unsigned char *base64_data = malloc(base64_data_len); - if (!base64_data) { - perror("malloc"); - return 1; - } - memcpy(base64_data, argv[1], base64_data_len); - - char *endptr = NULL; - errno = 0; - unsigned long difficulty = strtoul(argv[2], &endptr, 10); - if ((difficulty == ULONG_MAX && errno == ERANGE) || *endptr != '\0' - || difficulty > 256) { - fprintf(stderr, "invalid difficulty value\n"); - free(base64_data); - return 1; - } - - BIO *b64 = BIO_new(BIO_f_base64()); - BIO *bmem = BIO_new_mem_buf(base64_data, (int)base64_data_len); - if (!b64 || !bmem) { - fprintf(stderr, "BIO_new/BIO_new_mem_buf\n"); - free(base64_data); - return 1; - } - - BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); - b64 = BIO_push(b64, bmem); - - size_t decoded_cap = base64_data_len; - unsigned char *decoded = malloc(decoded_cap); - if (!decoded) { - perror("malloc"); - BIO_free_all(b64); - free(base64_data); - return 1; - } - - int decoded_len = BIO_read(b64, decoded, (int)decoded_cap); - if (decoded_len < 0) { - fprintf(stderr, "BIO_read\n"); - BIO_free_all(b64); - free(base64_data); - free(decoded); - return 1; - } - BIO_free_all(b64); - free(base64_data); - - EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); - if (!mdctx) { - fprintf(stderr, "EVP_MD_CTX_new\n"); - free(decoded); - return 1; - } - - size_t len = EVP_MD_size(EVP_sha256()); - unsigned char digest[len]; - size_t next = 0; - - while (1) { - if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) { - fprintf(stderr, "EVP_DigestInit_ex\n"); - EVP_MD_CTX_free(mdctx); - free(decoded); - return 1; - } - if (EVP_DigestUpdate(mdctx, decoded, decoded_len) != 1) { - fprintf(stderr, "EVP_DigestUpdate(data)\n"); - EVP_MD_CTX_free(mdctx); - free(decoded); - return 1; - } - if (EVP_DigestUpdate(mdctx, &next, sizeof(next)) != 1) { - fprintf(stderr, "EVP_DigestUpdate(next)\n"); - EVP_MD_CTX_free(mdctx); - free(decoded); - return 1; - } - if (EVP_DigestFinal_ex(mdctx, digest, NULL) != 1) { - fprintf(stderr, "EVP_DigestFinal_ex\n"); - EVP_MD_CTX_free(mdctx); - free(decoded); - return 1; - } - if (validate_bit_zeros(digest, difficulty)) { - break; - } - next++; - if (!next) { - fprintf(stderr, "unsigned integer overflow\n"); - EVP_MD_CTX_free(mdctx); - free(decoded); - return 1; - } - } - EVP_MD_CTX_free(mdctx); - free(decoded); - - BIO *b64_out = BIO_new(BIO_f_base64()); - BIO *bmem_out = BIO_new(BIO_s_mem()); - if (!b64_out || !bmem_out) { - fprintf(stderr, "BIO_new\n"); - if (b64_out) - BIO_free_all(b64_out); - if (bmem_out) - BIO_free(bmem_out); - return 1; - } - BIO_set_flags(b64_out, BIO_FLAGS_BASE64_NO_NL); - b64_out = BIO_push(b64_out, bmem_out); - - if (BIO_write(b64_out, &next, sizeof(next)) < 0) { - fprintf(stderr, "BIO_write\n"); - BIO_free_all(b64_out); - return 1; - } - if (BIO_flush(b64_out) < 1) { - fprintf(stderr, "BIO_flush\n"); - BIO_free_all(b64_out); - return 1; - } - - BUF_MEM *bptr = NULL; - BIO_get_mem_ptr(b64_out, &bptr); - if (!bptr || !bptr->data) { - fprintf(stderr, "BIO_get_mem_ptr\n"); - BIO_free_all(b64_out); - return 1; - } - - write(STDOUT_FILENO, bptr->data, bptr->length); - write(STDOUT_FILENO, "\n", 1); - - BIO_free_all(b64_out); - return 0; -}` +const solverProgram = `` @@ -12,7 +12,7 @@ var ( ) func init() { - flag.UintVar(&global.NeedBits, "difficulty", 17, "leading zero bits required for the challenge") + flag.UintVar(&global.NeedBits, "difficulty", 20, "leading zero bits required for the challenge") flag.StringVar(&global.SourceURL, "source", "https://forge.lindenii.runxiyu.org/powxy/:/repos/powxy/", "url to the source code") flag.StringVar(&listenAddr, "listen", ":8081", "address to listen on") flag.StringVar(&destHost, "upstream", "http://127.0.0.1:8080", "destination url base to proxy to") @@ -8,4 +8,6 @@ var global = struct { NeedBitsReverse uint SourceURL string Version string -}{} +}{ + Version: "(no version)", +} @@ -21,6 +21,11 @@ type tparams struct { func main() { log.Fatal(http.ListenAndServe(listenAddr, http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + if strings.HasPrefix(request.URL.Path, "/.powxy/") { + http.StripPrefix("/.powxy/", http.FileServer(http.FS(resourcesFS))).ServeHTTP(writer, request) + return + } + cookie, err := request.Cookie("powxy") if err != nil { if !errors.Is(err, http.ErrNoCookie) { diff --git a/resources.go b/resources.go new file mode 100644 index 0000000..bf3b919 --- /dev/null +++ b/resources.go @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BSD-2-Clause +// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> + +package main + +import ( + "embed" +) + +//go:embed wasm/*.wasm +//go:embed static/* +var resourcesFS embed.FS diff --git a/static/solver.c b/static/solver.c new file mode 100644 index 0000000..d76d020 --- /dev/null +++ b/static/solver.c @@ -0,0 +1,182 @@ +// This is a reference implementation of the proof of work solver in C. +// For security reasons, it is recommended that you read and understand the +// entire program first if you actually want to run it. +// +// You need to have OpenSSL, and link with -lcrypto + +#include <openssl/evp.h> +#include <openssl/bio.h> +#include <openssl/buffer.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> + +bool validate_bit_zeros(const unsigned char *bs, unsigned long n) +{ + unsigned long q = n / CHAR_BIT; + unsigned char r = n % CHAR_BIT; + + for (unsigned long i = 0; i < q; i++) { + if (bs[i] != 0) + return false; + } + + if (r > 0) { + unsigned char mask = + (unsigned char)(UCHAR_MAX << (CHAR_BIT - r)); + if (bs[q] & mask) + return false; + } + + return true; +} + +int main(int argc, char **argv) +{ + if (argc < 3) { + fprintf(stderr, "usage: %s <base64_data> <difficulty>\n", + argv[0]); + return 1; + } + + size_t base64_data_len = strlen(argv[1]); + unsigned char *base64_data = malloc(base64_data_len); + if (!base64_data) { + perror("malloc"); + return 1; + } + memcpy(base64_data, argv[1], base64_data_len); + + char *endptr = NULL; + errno = 0; + unsigned long difficulty = strtoul(argv[2], &endptr, 10); + if ((difficulty == ULONG_MAX && errno == ERANGE) || *endptr != '\0' + || difficulty > 256) { + fprintf(stderr, "invalid difficulty value\n"); + free(base64_data); + return 1; + } + + BIO *b64 = BIO_new(BIO_f_base64()); + BIO *bmem = BIO_new_mem_buf(base64_data, (int)base64_data_len); + if (!b64 || !bmem) { + fprintf(stderr, "BIO_new/BIO_new_mem_buf\n"); + free(base64_data); + return 1; + } + + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + b64 = BIO_push(b64, bmem); + + size_t decoded_cap = base64_data_len; + unsigned char *decoded = malloc(decoded_cap); + if (!decoded) { + perror("malloc"); + BIO_free_all(b64); + free(base64_data); + return 1; + } + + int decoded_len = BIO_read(b64, decoded, (int)decoded_cap); + if (decoded_len < 0) { + fprintf(stderr, "BIO_read\n"); + BIO_free_all(b64); + free(base64_data); + free(decoded); + return 1; + } + BIO_free_all(b64); + free(base64_data); + + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + if (!mdctx) { + fprintf(stderr, "EVP_MD_CTX_new\n"); + free(decoded); + return 1; + } + + size_t len = EVP_MD_size(EVP_sha256()); + unsigned char digest[len]; + size_t next = 0; + + while (1) { + if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) { + fprintf(stderr, "EVP_DigestInit_ex\n"); + EVP_MD_CTX_free(mdctx); + free(decoded); + return 1; + } + if (EVP_DigestUpdate(mdctx, decoded, decoded_len) != 1) { + fprintf(stderr, "EVP_DigestUpdate(data)\n"); + EVP_MD_CTX_free(mdctx); + free(decoded); + return 1; + } + if (EVP_DigestUpdate(mdctx, &next, sizeof(next)) != 1) { + fprintf(stderr, "EVP_DigestUpdate(next)\n"); + EVP_MD_CTX_free(mdctx); + free(decoded); + return 1; + } + if (EVP_DigestFinal_ex(mdctx, digest, NULL) != 1) { + fprintf(stderr, "EVP_DigestFinal_ex\n"); + EVP_MD_CTX_free(mdctx); + free(decoded); + return 1; + } + if (validate_bit_zeros(digest, difficulty)) { + break; + } + next++; + if (!next) { + fprintf(stderr, "unsigned integer overflow\n"); + EVP_MD_CTX_free(mdctx); + free(decoded); + return 1; + } + } + EVP_MD_CTX_free(mdctx); + free(decoded); + + BIO *b64_out = BIO_new(BIO_f_base64()); + BIO *bmem_out = BIO_new(BIO_s_mem()); + if (!b64_out || !bmem_out) { + fprintf(stderr, "BIO_new\n"); + if (b64_out) + BIO_free_all(b64_out); + if (bmem_out) + BIO_free(bmem_out); + return 1; + } + BIO_set_flags(b64_out, BIO_FLAGS_BASE64_NO_NL); + b64_out = BIO_push(b64_out, bmem_out); + + if (BIO_write(b64_out, &next, sizeof(next)) < 0) { + fprintf(stderr, "BIO_write\n"); + BIO_free_all(b64_out); + return 1; + } + if (BIO_flush(b64_out) < 1) { + fprintf(stderr, "BIO_flush\n"); + BIO_free_all(b64_out); + return 1; + } + + BUF_MEM *bptr = NULL; + BIO_get_mem_ptr(b64_out, &bptr); + if (!bptr || !bptr->data) { + fprintf(stderr, "BIO_get_mem_ptr\n"); + BIO_free_all(b64_out); + return 1; + } + + write(STDOUT_FILENO, bptr->data, bptr->length); + write(STDOUT_FILENO, "\n", 1); + + BIO_free_all(b64_out); + return 0; +} diff --git a/static/solver.js b/static/solver.js new file mode 100644 index 0000000..a3f8366 --- /dev/null +++ b/static/solver.js @@ -0,0 +1,34 @@ +let wasm_instance = null; +let wasm_exports = null; + +async function load_wasm() { + let response = await fetch("/.powxy/wasm/solver.wasm"); + let { instance } = await WebAssembly.instantiateStreaming(response, { + env: { + memory: new WebAssembly.Memory({ initial: 1 }) + } + }); + wasm_instance = instance; + wasm_exports = instance.exports; +} + +onmessage = async function(e) { + let { identifier_bytes, difficulty } = e.data; + + if (!wasm_instance) { + await load_wasm(); + } + + let ptr = wasm_exports.get_challenge_ptr(); + let memory = new Uint8Array(wasm_instance.exports.memory.buffer, ptr, 32); + memory.set(identifier_bytes); + + let nonce = wasm_exports.solve(difficulty); + + let buf = new ArrayBuffer(8); + let view = new DataView(buf); + view.setBigUint64(0, BigInt(nonce), true); + let nonce_bytes = new Uint8Array(buf); + + postMessage({ nonce, nonce_bytes }); +}; diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..6ca536b --- /dev/null +++ b/static/style.css @@ -0,0 +1,121 @@ +html { + font-family: sans-serif; + background-color: var(--background-color); + color: var(--text-color); + --radius-1: 0.32rem; + --background-color: hsl(0, 0%, 100%); + --text-color: hsl(0, 0%, 0%); + --link-color: hsl(320, 50%, 36%); + --light-text-color: hsl(0, 0%, 45%); + --darker-border-color: hsl(0, 0%, 72%); + --lighter-border-color: hsl(0, 0%, 85%); + --text-decoration-color: hsl(0, 0%, 72%); + --darker-box-background-color: hsl(0, 0%, 92%); + --lighter-box-background-color: hsl(0, 0%, 95%); + --primary-color: hsl(320, 50%, 36%); + --primary-color-contrast: hsl(320, 0%, 100%); + --danger-color: #ff0000; + --danger-color-contrast: #ffffff; +} + +@media (prefers-color-scheme: dark) { + html { + --background-color: hsl(0, 0%, 0%); + --text-color: hsl(0, 0%, 100%); + --link-color: hsl(320, 50%, 76%); + --light-text-color: hsl(0, 0%, 78%); + --darker-border-color: hsl(0, 0%, 35%); + --lighter-border-color: hsl(0, 0%, 25%); + --text-decoration-color: hsl(0, 0%, 30%); + --darker-box-background-color: hsl(0, 0%, 20%); + --lighter-box-background-color: hsl(0, 0%, 15%); + } +} + +body { + margin: 0; + padding: 1rem; +} + +main { + max-width: 720px; + margin: 0 auto; +} + +*:focus-visible { + outline: 1.5px var(--primary-color) solid; +} + +section { + margin: 0; +} + +label { + display: block; + font-style: italic; + margin-top: 1rem; + margin-bottom: 0.5rem; +} + +h1 { + margin-top: 0; +} + +p, summary { + line-height: 1.2; + font-size: 1rem; +} + +a { + color: var(--link-color); + text-decoration-color: var(--text-decoration-color); +} + +input[type="text"] { + font-family: monospace; + font-size: 1rem; + background-color: var(--lighter-box-background-color); + color: var(--text-color); + width: 100%; + padding: 0.5rem; + border-radius: var(--radius-1); + border: none; + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.15); + margin-bottom: 1rem; + box-sizing: border-box; +} + +input[type="submit"] { + padding: 0.5rem 1rem; + background-color: var(--primary-color); + color: var(--primary-color-contrast); + border: none; + border-radius: var(--radius-1); + cursor: pointer; +} + +input[readonly] { + background-color: var(--lighter-box-background-color); + color: var(--text-color); + cursor: text; +} + +details { + margin-top: 2rem; + background-color: var(--lighter-box-background-color); + padding: 0.5rem; + border-radius: var(--radius-1); + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.15); +} + +pre { + overflow-x: auto; + display: block; + white-space: pre-wrap; + word-break: break-word; +} + +#solver_status { + color: var(--light-text-color); + margin-top: 1rem; +} @@ -9,7 +9,7 @@ import ( "text/template" ) -//go:embed challenge.tmpl +//go:embed challenge.html var tmplString string var tmpl *template.Template diff --git a/wasm/.gitignore b/wasm/.gitignore new file mode 100644 index 0000000..89d9a96 --- /dev/null +++ b/wasm/.gitignore @@ -0,0 +1 @@ +/a.out.wasm diff --git a/wasm/sha256.c b/wasm/sha256.c new file mode 100644 index 0000000..b6160af --- /dev/null +++ b/wasm/sha256.c @@ -0,0 +1,163 @@ +/********************************************************************* +* Filename: sha256.c +* Author: Brad Conte (brad AT bradconte.com) +* Copyright: Identified to be public domain by ducky +* Disclaimer: This code is presented "as is" without any guarantees. +* Details: Implementation of the SHA-256 hashing algorithm. + SHA-256 is one of the three algorithms in the SHA2 + specification. The others, SHA-384 and SHA-512, are not + offered in this implementation. + Algorithm specification can be found here: + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf + This implementation uses little endian byte order. +*********************************************************************/ + +/*************************** HEADER FILES ***************************/ +#include "sha256.h" + + +/****************************** MACROS ******************************/ +#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) +#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b)))) + +#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22)) +#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25)) +#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3)) +#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10)) + +#define memset(ptr, value, num) \ + do { \ + for (size_t i = 0; i < (num); i++) \ + ((unsigned char *)(ptr))[i] = (unsigned char)(value); \ + } while (0) + +/**************************** VARIABLES *****************************/ +static const WORD k[64] = { + 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 DEFINITIONS ***********************/ +void sha256_transform(SHA256_CTX *ctx, const BYTE data[]) +{ + WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; + + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); + for ( ; i < 64; ++i) + m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 64; ++i) { + t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i]; + t2 = EP0(a) + MAJ(a,b,c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +void sha256_init(SHA256_CTX *ctx) +{ + ctx->datalen = 0; + ctx->bitlen = 0; + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; +} + +void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len) +{ + WORD i; + + for (i = 0; i < len; ++i) { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 64) { + sha256_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } +} + +void sha256_final(SHA256_CTX *ctx, BYTE hash[]) +{ + WORD i; + + i = ctx->datalen; + + // Pad whatever data is left in the buffer. + if (ctx->datalen < 56) { + ctx->data[i++] = 0x80; + while (i < 56) + ctx->data[i++] = 0x00; + } + else { + ctx->data[i++] = 0x80; + while (i < 64) + ctx->data[i++] = 0x00; + sha256_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + + // Append to the padding the total message's length in bits and transform. + ctx->bitlen += ctx->datalen * 8; + ctx->data[63] = ctx->bitlen; + ctx->data[62] = ctx->bitlen >> 8; + ctx->data[61] = ctx->bitlen >> 16; + ctx->data[60] = ctx->bitlen >> 24; + ctx->data[59] = ctx->bitlen >> 32; + ctx->data[58] = ctx->bitlen >> 40; + ctx->data[57] = ctx->bitlen >> 48; + ctx->data[56] = ctx->bitlen >> 56; + sha256_transform(ctx, ctx->data); + + // Since this implementation uses little endian byte ordering and SHA uses big endian, + // reverse all the bytes when copying the final state to the output hash. + for (i = 0; i < 4; ++i) { + hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff; + hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff; + hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff; + hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff; + hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff; + hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff; + hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff; + hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff; + } +} diff --git a/wasm/sha256.h b/wasm/sha256.h new file mode 100644 index 0000000..ff07d85 --- /dev/null +++ b/wasm/sha256.h @@ -0,0 +1,34 @@ +/********************************************************************* +* Filename: sha256.h +* Author: Brad Conte (brad AT bradconte.com) +* Copyright: Identified to be public domain by ducky +* Disclaimer: This code is presented "as is" without any guarantees. +* Details: Defines the API for the corresponding SHA1 implementation. +*********************************************************************/ + +#ifndef SHA256_H +#define SHA256_H + +/*************************** HEADER FILES ***************************/ +#include <stddef.h> + +/****************************** MACROS ******************************/ +#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest + +/**************************** DATA TYPES ****************************/ +typedef unsigned char BYTE; // 8-bit byte +typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines + +typedef struct { + BYTE data[64]; + WORD datalen; + unsigned long long bitlen; + WORD state[8]; +} SHA256_CTX; + +/*********************** FUNCTION DECLARATIONS **********************/ +void sha256_init(SHA256_CTX *ctx); +void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len); +void sha256_final(SHA256_CTX *ctx, BYTE hash[]); + +#endif // SHA256_H diff --git a/wasm/solver.c b/wasm/solver.c new file mode 100644 index 0000000..f1c4fa6 --- /dev/null +++ b/wasm/solver.c @@ -0,0 +1,51 @@ +#include "sha256.h" + +unsigned char challenge[32]; + +char validate_hash(unsigned char *hash, unsigned char zero_bit_count) { + unsigned char q = zero_bit_count / 8; + unsigned char r = zero_bit_count % 8; + + for (unsigned char i = 0; i < q; i++) { + if (hash[i] != 0) { + return 0; + } + } + if (r > 0) { + unsigned char mask = (unsigned char)(0xFF << (8 - r)); + if (hash[q] & mask) { + return 0; + } + } + + return 1; +} + +unsigned char *get_challenge_ptr() { + return challenge; +} + +unsigned long long solve(unsigned char difficulty) { + unsigned long long nonce; + SHA256_CTX context; + + unsigned char hash[32]; + + nonce = 0; + + while(1) { + sha256_init(&context); + sha256_update(&context, challenge, sizeof(challenge)); + sha256_update(&context, (unsigned char*)(&nonce), sizeof(nonce)); + sha256_final(&context, hash); + + if(validate_hash(hash, difficulty)) { + // we did it!! + break; + } + + nonce++; + } + + return nonce; +} |