Skip to content

Drop version and generate_version from the key registration payload - #48

Open
santiagomed wants to merge 7 commits into
cursor/signed-read-receipts-1119from
cursor/registered-key-version-1119
Open

santiagomed wants to merge 7 commits into
cursor/signed-read-receipts-1119from
cursor/registered-key-version-1119

Conversation

@santiagomed

@santiagomed santiagomed commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Problem

generate_keypairs() filled the registration payload's version with a local millisecond timestamp (Date.now() in JS) and also stored that timestamp as the signing-key version. The keys are registered under the public_key_version the registration returns, not that timestamp. Unless the app then called set_identity(user_id, public_key_version), every signed action (message delete and edit, reactions, conversation-key changes, group changes) carried the timestamp as its public_key_version and was rejected.

Stacked on #49, which is stacked on #47.

Changes

  • The registration payload carries only public_key. PublicKeyRegistrationPayload drops version and generate_version in every binding (Rust, Python, Go, JS/WASM, JVM, .NET). ChatCore::generate_keypairs_with_version is removed, and the WASM binding calls generate_keypairs.
  • generate_keypairs sets no signing-key version. After registering the keys, call set_identity(user_id, public_key_version) with the version the registration returns, or pass it to import_keys / import_keys_with_version when loading saved keys. Signing with no version fails in the SDK with signing_key_version is not set: pass it explicitly or call set_identity() first, instead of sending a version that gets rejected.
  • Examples stop sending the two fields and take the registered version only from the X API (the POST response, or the matching entry of GET /2/users/:id/public_keys). If neither has it, the register scripts stop, and a re-run resumes the saved identity.
  • Example bots require CHAT_SIGNING_KEY_VERSION when they load a key blob, instead of defaulting to "1". Each example's ChatCore refuses set_identity without a version (the Rust one now returns Result), the browser app asks for the key version next to the blob, and the Juicebox config helpers no longer turn a missing public_key_version into "1".
  • Docs: the README, docs/API.md, the binding READMEs, and the doc comments say to call set_identity with the public_key_version the registration returns. CHANGELOG.md gains a breaking-changes entry.

Breaking changes

  • Code that reads the payload's version or generate_version (Version / GenerateVersion in Go and .NET, generateVersion in JS) must stop; POST the payload as returned.
  • Rust callers of ChatCore::generate_keypairs_with_version switch to generate_keypairs.
  • An app that generated keys and signed without setting a version used to send the timestamp, which was rejected. It now gets the SDK error above. Apps that already call set_identity with the registered version, or pass a version to import_keys, need no changes.
  • The example bots stop at startup if CHAT_SIGNING_KEY_VERSION is unset. Set it to the public_key_version the register script prints.

Merge order

This needs the X API to accept public-key registrations without version on POST /2/users/:id/public_keys. Merge and release it only after that is live: until then the endpoint requires version, so it rejects the payload this SDK returns. It is the top of the stack (#47 → #49 → #48), so #49 can merge without waiting for it.

Testing

  • On the stack, locally green: fmt, clippy -D warnings, cargo-deny, workspace tests, WASM build, Python, JS (Node 20 and 18), JVM, .NET, Go, and all six example suites.
  • Every binding suite pins that generated keys sign only once the registered version is set, and that restored keys carry the version import_keys was given. The Python test passes MessageDeleteParams, since Python takes params classes in this release, and the core tests use numeric sender ids, which per-message keys require.

Notes

  • The Go prebuilt archives are refreshed by the prebuilt sync workflow's commits on this branch (chore(go): sync libchat_xdk_go.a), not by hand.
  • Rebased onto Sign read receipts: add prepare_mark_conversation_read #49. The history is condensed into four commits: core and bindings, examples, docs, changelog.
Open in Web Open in Cursor 

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ santiagomed
❌ github-actions[bot]
You have signed the CLA already but the status is still pending? Let us recheck it.

@cursor cursor Bot changed the title Stop recording the registration placeholder as the signing-key version Drop version and generate_version from the key registration payload Oct 16, 2026
…ion payload

PublicKeyRegistrationPayload carries only public_key in every binding, and
generate_keypairs no longer records a signing-key version: the keys are
registered under the public_key_version the X API returns, so a local
timestamp version only produced signatures that were rejected. Call
set_identity with the registered version, or pass it to import_keys when
loading saved keys; signing without a version fails in the SDK with the
missing signing_key_version error. ChatCore::generate_keypairs_with_version
is removed.

Every binding suite pins that generated keys sign only once the registered
version is set, and that restored keys carry the version import_keys was
given.
The register scripts stop sending version and generate_version, and read the
registered public_key_version from the X API (the POST response, or the
matching entry of GET /2/users/:id/public_keys); when neither has it they
stop, and a re-run resumes the saved identity. The bots require
CHAT_SIGNING_KEY_VERSION when they load a key blob instead of defaulting to
"1", each example's ChatCore refuses set_identity without a version, the
browser app asks for the key version next to the blob, and the Juicebox
config helpers no longer turn a missing public_key_version into "1".
@santiagomed
santiagomed force-pushed the cursor/registered-key-version-1119 branch from f933d5b to 79d5b89 Compare October 16, 2026 23:53
@cursor
cursor Bot changed the base branch from main to cursor/signed-read-receipts-1119 October 16, 2026 23:54
@santiagomed
santiagomed added this pull request to stack #42 October 17, 2026 01:31
Both this branch and its base re-synced the Go prebuilt archives after the
rebase onto the stack. The base changed nothing else, so keep this branch's
archives: they are built from its code, which already contains the base's.
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