Skip to content

Remote Signal keys for zero-knowledge linked devices - #48

Open
purpshell wants to merge 6 commits into
purpshell/shadow-clientfrom
polymorfa/remote-cipher
Open

purpshell wants to merge 6 commits into
purpshell/shadow-clientfrom
polymorfa/remote-cipher

Conversation

@purpshell

@purpshell purpshell commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Adds a linked-device client whose Signal private keys, sessions, and message plaintext live in a customer-run Signal store. HyperMeow keeps transport, receipts, retries, public prekey uploads, and QR pairing. It forwards each encrypted receive payload to RemoteSignal, receives delivery metadata plus the explicit own-device app-state-key-share exception, and transmits customer-built stanzas through SendPrebuiltStanza.

Remote upload batches now survive service restarts and requested-count changes. RemoteSignal.PreparePreKeyUpload returns the complete outstanding public batch, without truncation or fresh top-up. AcknowledgePreKeyUpload acknowledges the immutable captured token and full ID list only after a successful upload IQ. The literal last returned ID is checked; ID wrap never becomes a numeric cutoff. Lost ACK responses retain their exact snapshot and replay before another prepare. Fresh retry-receipt keys continue to use separate GeneratePreKeys(1) / MarkPreKeysUploaded operations.

This PR remains stacked on #43 and does not modify its branch. No main merge, tag, release, or registry publication has been performed.

Implementation and dependency revisions:

  • PR source: 7b77e9876c4058e6e2ec52f9ef688812826173ee, on previous remote/metadata source 4f60a67410c9c5d099d2e2f4fcf4facd8c42d777.
  • Identical patch carried with cherry-pick -x onto the ABprops-compatible integration source 3bbdfa9c5e0b252ded2535da8cc6ccf49fba1388, resulting in 7e3393b9a6f98eaedb5fe3fb44fdd944e808c143. ABprops guards/notifications and receive metadata remain intact. Stable patch ID matches on both commits: caa3bdb79fcc3fb66e96e711811151f06a71cc17.
  • Companion Signal service private source: 0ad97727526d317e6609ecdcdca4bbe6ca8c8786, consuming core 4b3b13cacd40a27573d0246386db553ef887539b and additive private protocol request tags 20/21 and result tag 18. Proto SHA256: fa043d9139c168ed6ef27fb6962cf56148a5f89a7d98875717d8c03f48d27754.
  • Backend adoption is still required: implement the two new interface methods, vendor/regenerate that exact protocol, preserve fresh exact-count decoding, and keep the ABprops-compatible dependency. These feature commits are not production main pseudo-versions.

Verification on both source and compatible heads:

  • Full go test -race -count=1 ./... passes on Go 1.25.14 and Go 1.26.6.
  • go build ./..., go vet ./..., generated MEX binding check, pre-commit run --all-files, and whitespace checks pass.
  • The regression that prepares 812 keys and then requests 50 failed before the fix and passes afterward. Full public batches, partial consumption, 24-bit wrap, wrong/nonlast ACK rejection, malformed tokens/keys/counts, defensive caller/remote/ACK snapshots, persistent and lost ACK failures, fresh retry-key separation, and concurrent prepare/mark/fresh-generation tests pass. Focused race tests also passed 10 repetitions.
  • Actual uploadPreKeys -> sendIQ tests capture the complete ordered public list, inject both transport failure and server IQ error, and prove zero ACK after either failure, including a following prepare. A second client instance recovers the full 812-key batch with a request for 50. Successful IQ precedes ACK; a lost ACK response is replayed exactly before a new prepare.
  • Existing receive timestamp/sender-alt, shadow-client, pairing, retry and ABprops tests remain in the full passing suites. prekeys.go, retry.go, and notification.go are unchanged by this patch.

These local IQ and remote-store fixtures do not prove a live WhatsApp upload, deployed service availability, server redelivery, or backend adoption. Service persistence/real-core proofs belong to its owning source and were not rerun by this HyperMeow slice. Pair-code pairing and bot-message-secret handling remain explicitly unsupported on the remote client; this patch does not add them.

Six-part assessment:

Part Status Evidence / remaining boundary
API Updated private library contract Two explicit RemoteSignal methods and public batch DTO; existing operations keep fresh semantics. Matching service protocol is pinned above; backend implementation is pending. Public REST/auth/scopes unchanged.
SDKs Updated HyperMeow; application SDKs unaffected Handwritten private adapter and test fakes updated. MIT application HTTP SDKs do not implement private prekey RPCs or import the Signal core.
CLI Unaffected No command, flag, credential, default, or output changes; operators need matching service/backend builds before activation.
Documentation Updated library documentation README explains durable batches, ACK ordering/replay, fresh keys and interface migration; Unreleased changelog updated. Monorepo customer release documentation remains in its coordinated owning change.
Feature release Blocked on integration and acceptance Existing ZK admission/runtime authority remains mandatory. No new flag, entitlement, meter or customer availability is granted by these methods. Main integration, package pinning, deployment and staging/provider acceptance remain separate gates.
Admin Unaffected by library state Private pending batches remain customer-store data. No hosted mirror, key inspection/reset/ACK lever or staff capability is introduced. Existing service/lease diagnostics remain the support boundary.

Dependency-ladder assessment:

  1. Libraries: HyperMeow updated; matching core/service source pinned; preserve authoritative reviewed-main publication gate.
  2. Runner/Bartender: backend RemoteSignal consumer adoption pending; Bartender has no new behavior.
  3. Temporal: no workflow payload, funding operation, accepted receipt or queued generation change.
  4. Customer API: private runner/service consumer update pending; public route shapes unchanged.
  5. Admin API: no new customer-service state or named staff operation.
  6. Logs/audit: no new payload logging or audit authority; malformed-response messages are fixed and do not echo batch material.
  7. Inspector: no desktop changes; provider acceptance remains a separate gate.
  8. Console: no UI or configuration authority change.
  9. Admin Console: no new staff view/action from this library-only slice.
  10. SDKs: application HTTP consumers unchanged; private adapter interface migration required.
  11. CLI: no runtime prekey command or local fallback.
  12. Docs: library contract/migration updated; coordinated public release docs owned by the monorepo.
  13. Help Center: no new customer workflow; release guidance remains coordinated with actual availability.
  14. Marketing: no new launch or universal reliability claim.

Crosscutting effects: prekey retry does not move private keys or plaintext into the runner, change immutable ZK custody, create a fresh session lease, grant scopes, or extend funding. QuickLink pairing can recover a pending prekey upload without inventing a replacement child or pairing authority; invitation/consent/expiry behavior remains backend-owned. Browser-tunnel transport and exact Origin/generation gates are unchanged; this patch proves no new custody/tunnel combination. Billing, Usage, analytics, product tracking and audit records gain no quantity or collection. Preserve existing delivery ordering, cancellation and feature authority. Rollout requires a matching service/backend before new operations are used; Unsupported fails closed without generating replacement keys. Rollback must preserve remote pending state and never convert custody.

NewRemoteSignalClient runs a linked device whose Signal private keys,
sessions and message plaintext live outside the client, behind the
RemoteSignal interface. The client keeps its socket, receipts, retry
receipts, prekey uploads and QR pairing, but never decrypts or encrypts:

- receive hands each <enc> payload to the remote store and gets back only
  delivery metadata (events.RemoteMessage), plus an app-state key share
  when an own device sent one; errors map to the same retry receipts and
  nacks as the local path;
- prekeys come from a remote-backed PreKeyStore that marks whole upload
  batches;
- pairing keeps the local HMAC and account-signature checks and takes the
  device signature from the store, refusing one over different details;
  the store learns its own JIDs before pairing is confirmed;
- incoming retry receipts ask the store to resend from its own record;
- SendPrebuiltStanza transmits stanzas the store built and waits for the
  ack. Local SendMessage and pair-code pairing are refused.

The device store holds only public material and is marked RemoteKeys; the
local Signal store panics instead of using its zero private keys.
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review: 1
  • dev

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 74462b35-7348-48db-ae9e-688355b8c667

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-10T02:22:23.579906Z d4c58f2 PR opened
🔒 Security Review ✅ Completed 2026-10-10T02:18:56.462406Z d4c58f2 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

golang.org/x/tools v0.50.0 and later require Go 1.26, so installing
goimports@latest fails the Go 1.25 build job on every new run. Pin
v0.49.0, the newest release on Go 1.25.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d4c58f25a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread retry.go
Comment thread remote_signal.go Outdated
Comment thread remote_signal.go
Comment thread remote_signal.go
Comment thread remote_signal.go Outdated
Comment thread send.go
Comment thread remote_signal.go
- Remote retry path calls PreRetryCallback (message unavailable) before resending.
- Retry-receipt prekeys from GenOnePreKey are marked published.
- A store transport error is neither acked nor retried; the server redelivers.
- Verify the remote device signature against the remote identity key.
- Reuse unuploaded prekeys and mark only the batch that was uploaded.
- SendFBMessage refuses local encryption on a remote client.
- Identity changes forget the sender's sessions in the remote store.
- RemoteSignal gains MigratePNToLID. On a remote client, the PN-to-LID
  migration in decryptMessages goes to the remote store (once per user
  after success) instead of the unused local session store.
- forgetRemoteIdentity sends hosted JIDs as their base user; the store
  clears hosted devices with it, matching the local cleanup.
- Document that PreRetryCallback gets a nil message on remote clients.
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.

1 participant