aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2025-03-23 09:51:53 +0800
committerRunxi Yu <me@runxiyu.org>2025-03-23 09:51:53 +0800
commit586c0e64d670eaedd141868ad857d74bfa96c6b8 (patch)
tree5c4c94cde5d136eda2bfe2b3a6271dbf956f4f77
parentUse C program instead of Python script to solve the challenge (diff)
downloadpowxy-586c0e64d670eaedd141868ad857d74bfa96c6b8.tar.gz
powxy-586c0e64d670eaedd141868ad857d74bfa96c6b8.tar.zst
powxy-586c0e64d670eaedd141868ad857d74bfa96c6b8.zip
Add JS solver
-rw-r--r--main.go85
1 files changed, 82 insertions, 3 deletions
diff --git a/main.go b/main.go
index fdf75d4..ce4fe1c 100644
--- a/main.go
+++ b/main.go
@@ -79,11 +79,89 @@ Encode your selected value in base64 and submit it below:
</form>
<br />
<details>
-<summary>
-Program to solve this
-</summary><pre>` + html.EscapeString(solverProgram) + `</pre>
+<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 65536 tries
+ if ((nonce & 0xFFFFn) === 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 {
@@ -173,6 +251,7 @@ func validateCookie(cookie *http.Cookie, expectedToken []byte) bool {
return subtle.ConstantTimeCompare(gotToken, expectedToken) == 1
}
+
func makeSignedToken(request *http.Request) []byte {
buf := make([]byte, 0, 2*sha256.Size)