diff options
author | Runxi Yu <me@runxiyu.org> | 2025-03-23 10:44:47 +0800 |
---|---|---|
committer | Runxi Yu <me@runxiyu.org> | 2025-03-23 10:44:47 +0800 |
commit | ef25d5fcc73b9d0cd2a2ac69fdee9d7a2dbfa9ed (patch) | |
tree | abd7f87e165ed1628a82884993c6c38e3fcd8f0f /tmpl.go | |
parent | Use httputil's reverse proxy (diff) | |
download | powxy-ef25d5fcc73b9d0cd2a2ac69fdee9d7a2dbfa9ed.tar.gz powxy-ef25d5fcc73b9d0cd2a2ac69fdee9d7a2dbfa9ed.tar.zst powxy-ef25d5fcc73b9d0cd2a2ac69fdee9d7a2dbfa9ed.zip |
Separate them into different files
Diffstat (limited to 'tmpl.go')
-rw-r--r-- | tmpl.go | 125 |
1 files changed, 125 insertions, 0 deletions
@@ -0,0 +1,125 @@ +package main + +import ( + "html" + "log" + "text/template" +) + +var tmpl *template.Template + +func init() { + var err error + tmpl, err = template.New("powxy").Parse(` +<!DOCTYPE html> +<html> +<head> +<title>Proof of Work Challenge</title> +</head> +<body> +<h1>Proof of Work Challenge</h1> +<p>This site is protected by <a href="https://forge.lindenii.runxiyu.org/powxy/:/repos/powxy/">Powxy</a>.</p> +<p>You must complete this proof of work challenge before you could access this site.</p> +{{- if .Message }} +<p><strong>{{ .Message }}</strong></p> +{{- end }} +<p>Select an nonce shorter than or equal to 32 bytes, such that when it is appended to the decoded form of the following base64 string, and a SHA-256 hash is taken as a whole, the first {{ .NeedBits }} bits of the SHA-256 hash are zeros. Within one octet, higher bits are considered to be in front of lower bits.</p> +<p>{{ .UnsignedTokenBase64 }}</p> +<form method="POST"> +<p> +Encode your selected nonce in base64 and submit it below: +</p> +<input name="powxy" type="text" /> +<input type="submit" value="Submit" /> +</form> +<br /> +<details> +<summary>Program to solve this</summary> +<pre>` + html.EscapeString(solverProgram) + `</pre> +</details> +<p id="solver_status"></p> +</body> +<script> +document.addEventListener("DOMContentLoaded", function() { + let challenge_b64 = "{{.UnsignedTokenBase64}}"; + let difficulty = {{.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 in JS..."; + + let solver_active = true; + form.addEventListener("submit", function() { + solver_active = false; + }); + + async function solve_pow() { + let token_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(token_bytes.length + 8); + candidate.set(token_bytes, 0); + candidate.set(new Uint8Array(buf), token_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 = "Solution found. Submitting..."; + form.submit(); + return; + } + + nonce++; + + // Update status every 256 tries + if ((nonce & 0x00FFn) === 0n) { + status_el.textContent = "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(); +}); +</script> +</html> +`) + if err != nil { + log.Fatal(err) + } +} |