fix: standard ALTCHA PoW + lower default volume to 65%
Replace custom PoW scheme with standard ALTCHA: SHA-256(salt+number)
solver, base64-encoded {algorithm,challenge,number,salt,signature} verify
payload. Add verbose logging to diagnose future failures.
Lower default audio volume from 0.8 to 0.65.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
941a486913
commit
719d4d9afd
2 files changed
+24
-34
No files matched your search
+23
-33
@@ -26,34 +26,20 @@ let _captchaCookie = null;
|
||||
let _captchaAt = 0;
|
||||
const CAPTCHA_TTL = 27 * 60 * 1000; // refresh 1 min before server's 28-min expiry
|
||||
|
||||
// Iterative SHA-256 PoW solver — yields every 1000 iterations to stay non-blocking
|
||||
// Standard ALTCHA PoW solver: find number such that SHA-256(salt + number) == challenge
|
||||
async function solveAltcha(challenge) {
|
||||
const { nonce, salt, cost, keyLength, keyPrefix } = challenge;
|
||||
const saltBuf = Buffer.from(salt, 'hex');
|
||||
const nonceBuf = Buffer.from(nonce, 'hex');
|
||||
const prefixBuf = Buffer.from(keyPrefix, 'hex');
|
||||
const startMs = Date.now();
|
||||
const BATCH = 1000;
|
||||
const { algorithm = 'SHA-256', challenge: target, salt, maxnumber = 1_000_000 } = challenge;
|
||||
if (algorithm !== 'SHA-256') throw new Error(`ALTCHA: unsupported algorithm ${algorithm}`);
|
||||
const startMs = Date.now();
|
||||
|
||||
for (let base = 0; base < 1_000_000; base += BATCH) {
|
||||
for (let i = 0; i < BATCH; i++) {
|
||||
const counter = base + i;
|
||||
const ctrBuf = Buffer.allocUnsafe(4);
|
||||
ctrBuf.writeUInt32BE(counter);
|
||||
// password = nonce || counter_be32 ; initial = salt || password
|
||||
const initial = Buffer.concat([saltBuf, nonceBuf, ctrBuf]);
|
||||
let derived = crypto.createHash('sha256').update(initial).digest().slice(0, keyLength);
|
||||
for (let c = 0; c < cost; c++) {
|
||||
derived = crypto.createHash('sha256').update(derived).digest().slice(0, keyLength);
|
||||
}
|
||||
if (derived.slice(0, prefixBuf.length).equals(prefixBuf)) {
|
||||
return { counter, derivedKey: derived.toString('hex'), time: Date.now() - startMs };
|
||||
}
|
||||
for (let number = 0; number <= maxnumber; number++) {
|
||||
const hash = crypto.createHash('sha256').update(salt + String(number)).digest('hex');
|
||||
if (hash === target) {
|
||||
return { number, time: Date.now() - startMs };
|
||||
}
|
||||
// Yield to event loop between batches
|
||||
await new Promise(r => setImmediate(r));
|
||||
if (number % 10_000 === 0) await new Promise(r => setImmediate(r));
|
||||
}
|
||||
throw new Error('ALTCHA: no solution found within 1M iterations');
|
||||
throw new Error('ALTCHA: no solution found within maxnumber');
|
||||
}
|
||||
|
||||
function httpsGet(url) {
|
||||
@@ -97,24 +83,28 @@ function httpsPost(url, body, extraHeaders) {
|
||||
async function getAltchaCookie() {
|
||||
if (_captchaCookie && Date.now() - _captchaAt < CAPTCHA_TTL) return _captchaCookie;
|
||||
|
||||
// 1. Fetch challenge — response may wrap params in a "parameters" key
|
||||
// 1. Fetch challenge — standard ALTCHA returns { algorithm, challenge, salt, signature, maxnumber }
|
||||
const chRes = await httpsGet(`${SQUID_BASE}/api/altcha/challenge`);
|
||||
if (chRes.status !== 200) throw new Error(`ALTCHA challenge HTTP ${chRes.status}`);
|
||||
if (chRes.status !== 200) throw new Error(`ALTCHA challenge HTTP ${chRes.status}: ${chRes.body.slice(0,200)}`);
|
||||
const chBody = JSON.parse(chRes.body);
|
||||
const challenge = chBody.parameters ?? chBody; // unwrap { parameters: {...} } if present
|
||||
log('altcha', `🔐 ALTCHA challenge: alg=${challenge.algorithm} max=${challenge.maxnumber}`);
|
||||
|
||||
// 2. Solve PoW
|
||||
const solution = await solveAltcha(challenge);
|
||||
log('altcha', `🔐 ALTCHA gelöst in ${solution.time}ms (number=${solution.number})`);
|
||||
|
||||
// 3. Verify — mirror the challenge response structure: { parameters, signature } + solution fields
|
||||
const verifyBody = {
|
||||
parameters: challenge,
|
||||
solution: { number: solution.counter, derivedKey: solution.derivedKey },
|
||||
signature: chBody.signature,
|
||||
// 3. Verify — standard ALTCHA payload: base64(JSON { algorithm, challenge, number, salt, signature })
|
||||
const payloadObj = {
|
||||
algorithm: challenge.algorithm || 'SHA-256',
|
||||
challenge: challenge.challenge,
|
||||
number: solution.number,
|
||||
salt: challenge.salt,
|
||||
signature: challenge.signature,
|
||||
};
|
||||
const payload = Buffer.from(JSON.stringify(verifyBody)).toString('base64');
|
||||
const payload = Buffer.from(JSON.stringify(payloadObj)).toString('base64');
|
||||
const vRes = await httpsPost(`${SQUID_BASE}/api/altcha/verify`, { payload });
|
||||
if (vRes.status !== 200) throw new Error(`ALTCHA verify HTTP ${vRes.status}`);
|
||||
if (vRes.status !== 200) throw new Error(`ALTCHA verify HTTP ${vRes.status}: ${vRes.body.slice(0,200)}`);
|
||||
|
||||
// Parse Set-Cookie: captcha_verified_at=<value>; ...
|
||||
const setCookie = [vRes.headers['set-cookie']].flat().filter(Boolean);
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ window.addEventListener('load', () => {
|
||||
}
|
||||
});
|
||||
|
||||
S.audio.volume = 0.8;
|
||||
S.audio.volume = 0.65;
|
||||
|
||||
// Re-register MediaSession handlers when returning from background/screen-off/flip
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
|
||||
Reference in new issue
Block a user