Skip to content

fix: Derive and store channel secrets for hashtag channels (#81) - #87

Merged
cwill747 merged 3 commits into
mainfrom
fix-81
Aug 9, 2026
Merged

fix: Derive and store channel secrets for hashtag channels (#81)#87
cwill747 merged 3 commits into
mainfrom
fix-81

Conversation

@cwill747

@cwill747 cwill747 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Fixes #81.

Problem

+ Add Channel wrote a row to channels but never to channel_secrets, so pyMC_core had no key for the channel and every send failed with Send failed: Channel 'bot' not in provided channels_config. Only #Public worked, because its secret is seeded as a constant at first launch.

Fix

MeshCore derives a hashtag channel's key as sha256("#" + name)[:32]. Verified independently of the issue report: sha256("#mesh")[:32] == 5b664cde0b08b220612113db980650f3, matching the published value. Names are lowercased first, matching the lowercase-alnum-and-hyphens convention the ecosystem uses — the key is the shared secret, so #Chicago and #chicago must not diverge.

  • ensure_channel() and send_message() now populate channel_secrets for group channels. Both send and receive work without a reconnect, since GroupTextHandler queries the channel db live.
  • A v6 -> v7 migration backfills channels created before this fix, so existing installs repair themselves instead of needing the manual INSERT from the issue.
  • #Public keeps its hardcoded constant. It is deliberately not the derived sha256("#public")[:32] (8b4b705b… vs the real 8b3387e9…), and backfilling it would break the default channel.

Credit and changes on top

Based on the patch contributed by @educationalpurposes in #81, with two fixes:

  1. Backfilled mixed-case channels still failed to send. The backfill stored the lowercased name but left channels.display_name mixed-case, and send_message passed display_name straight to pyMC_core's case-sensitive matcher — so a backfilled #Chicago still raised Channel 'Chicago' not in provided channels_config. Senders now resolve the name through channel_secrets instead of trusting the display name.
  2. Removing a channel destroyed imported secrets. A secret imported with radio_cli import-channel cannot be recovered; a derived hashtag secret always can. Only derived secrets are dropped now.

Verification

  • uv run pytest: 115 passed, 1 skipped. ruff check / ruff format --check clean.
  • End-to-end against the real PacketBuilder.create_group_datagram: derived (#bot), imported-PSK (#MyPrivate), and constant (#Public) channels all build packets.
  • Reproduced the pre-fix failure and the mixed-case backfill gap before fixing them; both are covered by new regression tests in tests/unit/test_channel_secrets.py.

🤖 Generated with Claude Code

"+ Add Channel" wrote a row to `channels` but never to `channel_secrets`,
so pyMC_core had no key for the channel and every send failed with
"Channel 'bot' not in provided channels_config". Only #Public worked,
because its secret is seeded as a constant at first launch.

MeshCore derives a hashtag channel's key as sha256("#" + name)[:32]
(verified: sha256("#mesh")[:32] == 5b664cde0b08b220612113db980650f3,
matching the published value). Names are lowercased first, matching the
lowercase-alnum-and-hyphens convention the ecosystem uses; the key *is*
the shared secret, so "#Chicago" and "#chicago" must not diverge.

- ensure_channel() and send_message() now populate channel_secrets for
  group channels, so both send and receive work without a reconnect
  (GroupTextHandler queries the channel db live).
- A v6 -> v7 migration backfills channels created before this fix, so
  existing installs repair themselves instead of needing a manual INSERT.
- #Public keeps its hardcoded constant; it is deliberately *not* the
  derived sha256("#public")[:32], and backfilling it would break the
  default channel.

Based on the patch contributed by @educationalpurposes in #81, with two
fixes on top:

- The backfill stored the lowercased name but left `channels.display_name`
  mixed-case, and send_message passed display_name to pyMC_core's
  case-sensitive matcher — so a backfilled "#Chicago" still failed to send.
  Senders now resolve the name through channel_secrets rather than
  trusting the display name.
- Removing a channel hard-deleted its secret, destroying secrets imported
  with `radio_cli import-channel`, which cannot be recovered. Only derived
  secrets are dropped now.

Closes #81

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cwill747
cwill747 merged commit 0945320 into main Aug 9, 2026
9 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.

Add Channel does not derive or store channel secret, causing send to fail

1 participant