Skip to content

fix(dht): BEP44 mutable signing — channel feeds now work — + channels app two-column layout, native streaming player, file detail, checker guard#24

Merged
SethMorrowSoftware merged 5 commits into
mainfrom
claude/relaxed-cerf-816pyn
Jun 28, 2026
Merged

fix(dht): BEP44 mutable signing — channel feeds now work — + channels app two-column layout, native streaming player, file detail, checker guard#24
SethMorrowSoftware merged 5 commits into
mainfrom
claude/relaxed-cerf-816pyn

Conversation

@SethMorrowSoftware

@SethMorrowSoftware SethMorrowSoftware commented Jun 27, 2026

Copy link
Copy Markdown
Owner

0. THE BUG: mutable DHT values were signed wrong, so no channel feed ever worked

Reported live: followers saw none of a publisher's files and publishers reached no one - symmetric, total failure.

btx_dht_put_mutable's signing callback set the entry e to the value but signed the raw value bytes. BEP44 signs the bencoded form (a string "hi" bencodes to "2:hi"), and every storing DHT node and every getter's libtorrent verifies the signature against that. Signing raw bytes yields a signature that verifies against nothing, so storing nodes reject the put and getters drop the item before emitting dht_mutable_item_alert - the dhtMutableItem event never fires and no feed is exchanged, in either direction. The put still returned BTX_OK (it only confirms the request wired up; signing runs later on the network thread), which is why nothing looked broken. Fix mirrors libtorrent's own examples/dht_put.cpp: bencode e, then sign. The GET side (entry_to_bytes) already returns string entries verbatim, so the round-trip is clean once signing is fixed.

Regression guard: the signing is factored into one helper used by both the production callback and a new btx::test::dht_mutable_sign_verifies hook, which signs through that helper and checks the result with ed25519_verify against the reconstructed BEP44 canonical message (verify_mutable_item itself is TORRENT_EXTRA_EXPORT, not in the shared lib). torrent_smoke_test asserts it verifies for empty and non-empty salt. Confirmed the guard bites: reintroducing the raw-value sign fails exactly those 3 checks; with the fix, 552 checks / 0 failures under gcc ASan+UBSan. Internal change only - no btx_* ABI symbol, record field, or LCB-visible behavior changed, so no ABI bump.

Note: the fix is in the native shim, so it only takes effect with a rebuilt torrentxt library. The committed src/code/ binaries refresh automatically on merge to main (CI commit-binaries); to test before then, rebuild the shim locally.


The rest of this branch (the channels-app work that surfaced the bug while testing it):

1. Self-test harness fix - is a string is not valid LiveCode

is a accepts only number/integer/boolean/point/rect/date/color. Replaced with btClearError + (btLastError() is empty). The rest of that OXT run then came back all-green (71/75 handlers).

2. Channels demo - file detail, streaming, inline progress

  • Inline progress bars in the transfers table (chProgressBar).
  • Selected-transfer file detail via btFileList: name, size, live per-file progress bar; refreshed each tick (repaint only on change), selection preserved by handle.
  • Sequential download via btSetSequentialDownload.

3. Two-column 720p layout + native streaming player

  • Two-column layout (1180x648) fits a 720p screen; supersedes the 880x900 single column. tabStops re-fit.
  • Native OXT player (chPlayer), hidden by default, appears over the transfer pane only while streaming, dismissed with Close. Stream points it at the largest video/audio file (chMainMediaFile) on the torrent's save path (btTorrentStatus["savePath"] + btFileList) and plays as the in-order download fills. Built inside try with an sHasPlayer gate. New chOpenPlayer/chCloseStream, wired into mouseUp.

4. tExt spells text + a static guard

tExt in chMediaKind is t-e-x-t = text; renamed to tSuffix. check-livecodescript.py gains rule 6: any t/p/s/k-prefixed name whose full lowercased spelling is a reserved token. CLAUDE.md gotcha 2 records it.

Verification

  • Shim: 552 checks / 0 failures under gcc ASan+UBSan; static gates (lint + golden + registry/ABI sync) green.
  • check-livecodescript: 5 files OK (incl. the new shadow rule).
  • The two-column layout and native player still want an OXT pass to confirm the player object on the target build; the DHT fix wants the cross-machine re-test that surfaced it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01371AXB4CUUke7enHHS8okc

claude added 2 commits June 27, 2026 22:06
First OXT run of the harness threw on:
    stAssert "btLastError is a string", (btLastError() is a string)
LiveCode's `is a <type>` operator only accepts number/integer/boolean/point/
rect/date/color - there is NO `is a string` type token, so it is a parse/runtime
error, not a failed assertion. (The static checker can't catch this - it does not
model LiveCode's type vocabulary.)

Replaced with a valid, deterministic check that also exercises btClearError:
    btClearError
    stAssert "btLastError is empty after btClearError", (btLastError() is empty)

Audited every other `is a` in the harness: all are `is a number` (valid) or
`there is a field` (existence) - this was the only invalid type token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01371AXB4CUUke7enHHS8okc
Three upgrades to the DHT channel demo, all using the now-runtime-confirmed v8
surface the demo previously ignored:

- Inline progress bars in the transfers table: the Progress column is now a
  monospaced [####------] 42% bar (chProgressBar, the flagship client's helper)
  instead of plain "42%".
- Selected-transfer file detail: click a transfer to see the files inside it via
  btFileList - name, size, and a LIVE per-file progress bar that climbs as the
  download advances. Updated each 1 s tick (guarded to repaint only on change),
  selection preserved across the repaint by torrent handle (sXferHandle map).
- "Stream Selected (in order)" button: flips btSetSequentialDownload on the
  selected transfer so a media release plays as it arrives instead of in random
  piece order.

Window grew to 900 tall to fit the new file panel below the transfers table;
Quick drop + log shifted down. New handlers: chProgressBar, chShowFiles,
chShowFilesForSelection, chStreamSelected, chSelectedXferLine,
chSelectedXferHandle. Tooltips + the in-app help + header doc updated.

Verified statically (check-livecodescript: 5 files OK; ASCII-clean; 54 balanced
handlers; btFileList + btSetSequentialDownload resolve to real public handlers,
both confirmed working in the OXT self-test run). Needs an OXT pass to confirm
the new layout and the live file-progress view.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01371AXB4CUUke7enHHS8okc
@SethMorrowSoftware SethMorrowSoftware changed the title tests: fix self-test harness — is a string is not valid LiveCode examples: channels app — file detail, streaming, inline progress bars (+ self-test fix) Jun 27, 2026
Rework the DHT channels demo so it fits a 720p (1280x720) screen and adds
real media playback:

- Two-column layout (1180x648): your-channel + follows on the left,
  transfers + selected-transfer file detail on the right, quick-drop and
  log full-width across the bottom. Replaces the 880x900 single column
  that overran 720p vertically. tabStops re-fit to the narrower fields.

- A native OXT player object (chPlayer), hidden by default, that appears
  over the transfer pane only while streaming and is dismissed with Close.
  Stream now points it at the largest video/audio file in the transfer
  (chMainMediaFile) on the torrent's save path and plays it as the
  in-order download fills (btSetSequentialDownload + btTorrentStatus
  savePath + btFileList). Video and audio are the only streamed kinds
  (chMediaKind extension classifier).

- The player is built defensively (try/sHasPlayer): a build without a
  player object still gets sequential download and a log hint, never a
  crash. New chOpenPlayer / chCloseStream commands, chCloseStream wired
  into mouseUp, tooltips + help text updated.

Verified statically (tools/check-livecodescript.py: 5 files OK); needs an
OXT pass to confirm the player object behaves on the target build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01371AXB4CUUke7enHHS8okc
@SethMorrowSoftware SethMorrowSoftware changed the title examples: channels app — file detail, streaming, inline progress bars (+ self-test fix) examples: channels app - file detail, in-app streaming player, 720p two-column layout, inline progress (+ self-test fix) Jun 27, 2026
claude added 2 commits June 27, 2026 23:06
…e class

`tExt` in chMediaKind (intended t + "Ext" for file extension) is literally
t-e-x-t = `text`, so xTalk evaluates it as the `text` keyword, not a
variable - it compiles and silently misbehaves. Renamed to `tSuffix`.

This slipped past the checker, so close the gap durably:

- check-livecodescript.py gains rule 6: any t/p/s/k-prefixed CamelCase name
  whose FULL lowercased spelling is a reserved token (text, the, time,
  title, target, send, set, start, stop, ...). Only reserved words that
  begin with a prefix letter can collide, so that is the whole carried set;
  the `[tpsk][A-Z]` shape guard means a normally-written lowercase keyword
  never matches - only an accidentally-keyword-spelling identifier does, and
  that is always a real bug. Applies to both .lcb and .livecodescript.

- CLAUDE.md gotcha 2 records the lesson with the tExt example.

Verified: checker flags `tExt` and leaves `tName`/`tSuffix` alone; all 5
tracked files pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01371AXB4CUUke7enHHS8okc
This is THE bug that made channel feeds silently fail end to end: a
follower never saw a publisher's releases, and a publisher's feed never
reached anyone - in both directions, for everyone.

btx_dht_put_mutable's signing callback set the entry `e` to the value but
then called sign_mutable_item over the RAW value bytes. BEP44 signs the
BENCODED form: a string value "hi" bencodes to "2:hi", and every storing
DHT node and every getter's libtorrent verifies the signature against that
bencoded form before accepting/surfacing the item. Signing the raw bytes
produced a signature that verifies against nothing, so:

  - storing nodes rejected the put (invalid signature), and
  - even if stored, a getter's libtorrent drops the item before emitting
    dht_mutable_item_alert,

so the dhtMutableItem event never fired and no feed was ever exchanged.
The put call still returned BTX_OK (it only confirms the request wired up;
the signing runs later on the network thread), which is why nothing looked
wrong. Mirrors libtorrent's own examples/dht_put.cpp: bencode e, then sign.
The GET side (entry_to_bytes) already returns string entries verbatim, so
the round-trip is clean once signing is fixed.

Guard so it cannot regress silently:
- Factor the signing into one helper (sign_mutable_string_entry) used by
  both the production put callback and a new test hook, so the test
  exercises the real path.
- New btx::test::dht_mutable_sign_verifies signs through that helper and
  checks the signature with ed25519_verify against the reconstructed BEP44
  canonical message (verify_mutable_item itself is TORRENT_EXTRA_EXPORT, not
  in the shared lib). torrent_smoke_test asserts it verifies for empty and
  non-empty salt.
- Confirmed the guard catches the bug: reintroducing the raw-value sign
  makes exactly those 3 checks fail; with the fix, 552 checks / 0 failures
  under gcc ASan+UBSan.

Internal change only - no btx_* ABI symbol, record field, or LCB-visible
behavior changed, so no ABI bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01371AXB4CUUke7enHHS8okc
@SethMorrowSoftware SethMorrowSoftware changed the title examples: channels app - file detail, in-app streaming player, 720p two-column layout, inline progress (+ self-test fix) fix(dht): BEP44 mutable signing — channel feeds now work — + channels app two-column layout, native streaming player, file detail, checker guard Jun 27, 2026
@SethMorrowSoftware SethMorrowSoftware marked this pull request as ready for review June 28, 2026 00:02
@SethMorrowSoftware SethMorrowSoftware merged commit bee5a4a into main Jun 28, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants