Skip to content

feat(chat-cipher): group invite cipher in the shared core - #1866

Merged
bmc08gt merged 2 commits into
code/cashfrom
feat/invite-crypto
Oct 28, 2026
Merged

bmc08gt merged 2 commits into
code/cashfrom
feat/invite-crypto

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

Adds the crypto for private-group invite links to libs/encryption/chat-cipher, so both apps call one implementation through :kmp:shared-core. No UI or app wiring.

What's in it

  • InviteCipher / DefaultInviteCipher: invite token, invite id and wrap key from the link's 32-byte secret; seal and open InviteKeyEnvelope (XChaCha20-Poly1305, signed with the inviter's Ed25519 key); seal and open InviteSecretEnvelope (the self-wrapped KeyEnvelope construction). It reuses DefaultChatCipher's HKDF, AEAD and X25519 steps, which go from private to internal.
  • openInviteKeyBundle checks in the order the InviteKeyEnvelope comment sets: wrapped_by, signature against the creator's key, the member's secret envelope, then the copy. It returns InviteBundleResult.Rejected(failsAt) rather than throwing.
  • InviteLink: builds https://app.flipcash.com/chat/invite#s=<unpadded base64url>, parses a fragment strictly (exactly one s=, canonical encoding of 32 bytes, other keys ignored), and classifies a URL as invite, invalid invite, chat or other so routers can match /chat/invite ahead of /chat/{uuid}.
  • SharedInviteCipher in SharedCoreKit, the Swift facade with Data in and out.

wrapKey stays internal. It never leaves the device, and the vector test reaches it from the same module.

One signature differs from the brief

openInviteKeyBundle takes inviteId, from InviteKeyBundle.invite_id. The secret envelope's info and aad both bind the invite id, so the secret can't be opened without it. The bundle cases in invite.json carry inviteId only as an output, so InviteVectorTest takes it from the fixture's successful bundle. Adding it as an input on the reject cases would make that explicit.

Tests

InviteVectorTest runs every section of invite.json (copied from the orchestrator's test-vectors/), with each reject case asserted at its failsAt step. InviteCipherTests runs the same fixture through the Swift facade; sealing under fixed nonces is internal to Kotlin, so the facade checks sealing by round trip.

Release

iOS needs the next shared-core release, 0.17.0, through publish-shared-core.yml workflow_dispatch. I haven't triggered it.

DefaultInviteCipher implements chat.v1.Invite, InviteKeyEnvelope and
InviteSecretEnvelope from flipcash2-protobuf-api: the invite token, wrap key
and invite id derived from the link's 32-byte secret, the invite's signed copy
of the chat key, and the secret wrapped for its owner. It reuses
DefaultChatCipher's HKDF, XChaCha20-Poly1305 and X25519 steps, so pairKey,
seal, randomNonce and ensureSodium go from private to internal.

openInviteKeyBundle checks wrapped_by, then the signature against the
creator's key, then the member's secret envelope, then the copy, and returns
the step that failed. It takes the bundle's invite_id because the secret
envelope's info and aad bind it, so the secret cannot be opened without it.

InviteLink encodes https://app.flipcash.com/chat/invite#s=<secret> in unpadded
base64url, parses a fragment strictly (one s= pair, canonical encoding of 32
bytes), and classifies a URL as invite, invalid invite, chat or other so a
router can match /chat/invite ahead of /chat/{uuid}.

None of the types holding a secret, token or chat key print their bytes.

InviteVectorTest runs every section of invite.json from the orchestrator
repo, including each reject case at its failsAt step.
SharedInviteCipher wraps DefaultInviteCipher and InviteLink with Data in and
out, maps InviteBundleResult to a thrown BundleError and InviteLinkRoute to a
Swift enum. InviteCipherTests runs invite.json through the facade; sealing
under the fixture's fixed nonces stays on the Kotlin side, so here sealing is
checked by round trip.
@bmc08gt bmc08gt self-assigned this Oct 28, 2026
@github-actions github-actions Bot added type: feature New functionality area: crypto Solana, keys, encryption, signing labels Oct 28, 2026
@bmc08gt
bmc08gt merged commit 62ad83c into code/cash Oct 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: crypto Solana, keys, encryption, signing type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant