fix(dht): BEP44 mutable signing — channel feeds now work — + channels app two-column layout, native streaming player, file detail, checker guard#24
Merged
Conversation
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
is a string is not valid LiveCodeRework 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
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 entryeto 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 emittingdht_mutable_item_alert- thedhtMutableItemevent never fires and no feed is exchanged, in either direction. The put still returnedBTX_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 ownexamples/dht_put.cpp: bencodee, 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_verifieshook, which signs through that helper and checks the result withed25519_verifyagainst the reconstructed BEP44 canonical message (verify_mutable_itemitself isTORRENT_EXTRA_EXPORT, not in the shared lib).torrent_smoke_testasserts 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 - nobtx_*ABI symbol, record field, or LCB-visible behavior changed, so no ABI bump.The rest of this branch (the channels-app work that surfaced the bug while testing it):
1. Self-test harness fix -
is a stringis not valid LiveCodeis aaccepts onlynumber/integer/boolean/point/rect/date/color. Replaced withbtClearError+(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
chProgressBar).btFileList: name, size, live per-file progress bar; refreshed each tick (repaint only on change), selection preserved by handle.btSetSequentialDownload.3. Two-column 720p layout + native streaming player
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 insidetrywith ansHasPlayergate. NewchOpenPlayer/chCloseStream, wired intomouseUp.4.
tExtspellstext+ a static guardtExtinchMediaKindist-e-x-t=text; renamed totSuffix.check-livecodescript.pygains rule 6: anyt/p/s/k-prefixed name whose full lowercased spelling is a reserved token. CLAUDE.md gotcha 2 records it.Verification
check-livecodescript: 5 files OK (incl. the new shadow rule).playerobject 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