Solve a room's SHA-256 CAPTCHA without the host #11

Closed
asxpi wants to merge 1 commit from asxpi/libtacky:feat/muc-pow into develop
Member

A room behind XEP-0158 holds our join until its challenge is answered.
libtacky already hands that challenge to the host as , but a
proof of work needs no person, and a host that shows nothing for it
leaves the join hanging until it times out.

When a room we are joining (muc IsJoining) sends a challenge whose form
names our occupant JID and offers SHA-256 with a label of at most six
hex digits, solve it in slices on the event loop and submit only the
proof of work, leaving out the image, question and ejabberd fallback
fields. Give up after 60 seconds, or on a refused answer, and fall back
to for any image the form had. Challenges from anyone else
still go to the host untouched.

The hashcash loop moves out of register.tcl into hashcash.tcl, so
registration and rooms share one solver. No wire method or event
changes.

Tested: tests/client/test_captcha.tcl 30/30 (16 new); the full suite
has one OMEMO failure that also fails on develop.

Part of a proof of concept across anotherim-server, libtacky and anotherim-js (branch feat/muc-pow in each). Each part is tested on its own; not yet run end to end.

🤖 Generated with Claude Code

A room behind XEP-0158 holds our join until its challenge is answered. libtacky already hands that challenge to the host as <Challenge>, but a proof of work needs no person, and a host that shows nothing for it leaves the join hanging until it times out. When a room we are joining (muc IsJoining) sends a challenge whose form names our occupant JID and offers SHA-256 with a label of at most six hex digits, solve it in slices on the event loop and submit only the proof of work, leaving out the image, question and ejabberd fallback fields. Give up after 60 seconds, or on a refused answer, and fall back to <Challenge> for any image the form had. Challenges from anyone else still go to the host untouched. The hashcash loop moves out of register.tcl into hashcash.tcl, so registration and rooms share one solver. No wire method or event changes. Tested: tests/client/test_captcha.tcl 30/30 (16 new); the full suite has one OMEMO failure that also fails on develop. Part of a proof of concept across anotherim-server, libtacky and anotherim-js (branch `feat/muc-pow` in each). Each part is tested on its own; not yet run end to end. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A room behind XEP-0158 holds our join until its challenge is answered.
libtacky already hands that challenge to the host as <Challenge>, but a
proof of work needs no person, and a host that shows nothing for it
leaves the join hanging until it times out.

When a room we are joining (muc IsJoining) sends a challenge whose form
names our occupant JID and offers SHA-256 with a label of at most six
hex digits, solve it in slices on the event loop and submit only the
proof of work, leaving out the image, question and ejabberd fallback
fields. Give up after 60 seconds, or on a refused answer, and fall back
to <Challenge> for any image the form had. Challenges from anyone else
still go to the host untouched.

The hashcash loop moves out of register.tcl into hashcash.tcl, so
registration and rooms share one solver. No wire method or event
changes.

Tested: tests/client/test_captcha.tcl 30/30 (16 new); the full suite
has one OMEMO failure that also fails on develop.

Assisted-by: Claude:opus-5-5
Owner

Thanks for this. In the meantime the same feature landed in 1.1.16 and was hardened in 1.1.17: the solve stops when the join ends, one solve per room, and the prefix size is capped. So this branch now conflicts and is superseded, and I'm closing it.

You did two things that develop does not, and we'll take them over in a small follow-up on develop, with credit:

  • the form's from must be the room we are joining (otherwise a room could get us to solve someone else's registration proof of work);
  • a much lower difficulty limit for rooms (6 hex digits rather than 20).
Thanks for this. In the meantime the same feature landed in 1.1.16 and was hardened in 1.1.17: the solve stops when the join ends, one solve per room, and the prefix size is capped. So this branch now conflicts and is superseded, and I'm closing it. You did two things that develop does not, and we'll take them over in a small follow-up on develop, with credit: - the form's `from` must be the room we are joining (otherwise a room could get us to solve someone else's registration proof of work); - a much lower difficulty limit for rooms (6 hex digits rather than 20).
ann closed this pull request 2026-11-01 22:19:55 +00:00
ann referenced this pull request from a commit 2026-11-01 23:04:07 +00:00
1.1.19: the README's version for the tree to be tagged v1.1.19 (the user, 2026-11-02: "build and release next version"), on develop 3de8f623 = 1.1.18 + four changes: omemo WarmBundles no longer refetches the bundle of a peer's device whose key we already hold, so a reconnect stops downloading every contact's known devices again (2733a3c5; an Android catch-up was mostly that); a chat's sends leave in the order they were made, held behind an earlier send still waiting for keys, a preview or an upload (issue 13, ae52b2e1); an OMEMO room message reaches every member with a usable key and names the rest in Message omemo_left_out and <MembersUnreachable sent>, failing only when no other member is reached (issue 12, 0e55608b, 071b9c65); a room's proof of work is solved only when pinned to that room, at most 6 hex digits and 60 s (from asxpi's PR #11, a43e8867, 3de8f623). tools/stats.py --readme --cov _build/cov regenerated with the main .git mounted, coverage from make coverage-all on 3de8f623 (Tcl 98.7 % procs, Rust 99.5 % lines, C 99.0 % lines; its tests/run.tcl 7001 total, 6991 passed, 0 failed, 10 skipped). Gate: a fresh build, tests/run.tcl -j 32 7001 total, 6991 passed, 0 failed, 10 skipped; test-rust 787 passed, 0 failed, 5 ignored; check-api, check-headers, check-native-neutral and wasm clean; integration on ejabberd 26.09 (mod_avatar, mod_vcard_xupdate) with omemo-bot: test_catchup, test_message, test_omemo, test_omemo2_interop, test_mucsub, test_album, test_sm_window, test_muc_captcha, test_muc_invite, test_muc_edit, test_register, test_gateway_register, test_avatar, test_jinglefile_s5b 86 passed, 0 failed, 3 skipped, test_tacky_avatar 5 and test_vcard4 4 passed; test_muc_captcha also on anotherim-server's room-pow image with and without captcha_cmd, 3 passed and 1 skipped each; test_avatar on Prosody 11 passed.

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
another-im/libtacky!11
No description provided.