Skip to content

feat(chat): handle the E2EE contract additions and bump the contract packages - #1573

Merged
bmc08gt merged 8 commits into
code/cashfrom
chore/sync-contracts-e3d25a85-9ebf55fe
Sep 25, 2026
Merged

bmc08gt merged 8 commits into
code/cashfrom
chore/sync-contracts-e3d25a85-9ebf55fe

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Blocked on publishing ocp-client-protocol 0.6.0 (code-payments/ocp-client-protocol#9) and flipcash2-client-protocol 0.12.0 (code-payments/flipcash2-client-protocol#18). Until both are on Maven Central, CI can't resolve the pins in gradle/libs.versions.toml. Built locally against both client branches through protoLocalRoot.

Handles the flipcash2 E2EE contract additions without implementing any encryption:

  • ENCRYPTION_NOT_ALLOWED from send and edit maps to SendMessageError.EncryptionNotAllowed / EditMessageError.EncryptionNotAllowed.
  • Content.encrypted maps to MessageContent.Encrypted(scheme, nonce, ciphertext) and renders as an unsupported-message tombstone ("This message isn't supported on this version"). The bytes are stored in Room, so messages received now can be decrypted locally once E2EE lands.
  • asContent() writes Encrypted back out byte for byte instead of throwing. Its only callers are sendMessage and editMessage; an edit or outbox retry on a message holding a received encrypted item would otherwise have crashed.
  • ChatMetadata gains creator and useE2ee, persisted in chat_metadata. The database moves to version 38 with an AutoMigration, and 38.json is exported. useE2ee changes no behaviour yet.
  • A push whose ChatMetadata carries only message_id keeps today's no-message path. The id is carried on PushChatMetadata.messageId; fetching it through GetMessage is a TODO in ProtobufToLocal.asPayload().

The ocp fiat_values_by_currency fields aren't wired in. The balance wrappers map into their own domain types, so exposing fiat values needs a modelling decision, not a sync.

iOS counterpart: code-payments/code-ios-app#870

SendMessageResponse and EditMessageResponse gain a result case for
EncryptedContent sent outside a DM. Map both to a new
SendMessageError.EncryptionNotAllowed / EditMessageError.EncryptionNotAllowed
alongside the existing Denied/Conflict cases; the client doesn't construct
EncryptedContent yet, so this is defensive.
Mirrors chat/v1 Metadata.creator (group chats) and Metadata.use_e2ee (DMs,
transitional). useE2ee is carried through but ignored behaviourally until
E2EE send is implemented.
messaging/v1 Content gains an encrypted case. Decoding it needs E2EE
crypto (X25519/HKDF/XChaCha20), which is a cross-platform parity hotspot
with no decision yet, so it isn't decoded here.

Add MessageContent.Encrypted through the domain and persistence mapping
and render it as a tombstone bubble, the same treatment as a deleted
message. Sending EncryptedContent stays unimplemented and fails fast.
push/v1 ChatMetadata.message moved into a message_ref oneof; long
messages now arrive as message_id only. Store the id on
PushChatMetadata so it isn't silently dropped, but leave the
GetMessage fetch as a TODO. message stays null and callers keep their
existing no-message sync path, so behaviour is unchanged for now.
MessageContent.Encrypted was a marker data object, so Room only ever stored
"we got an encrypted message here" and dropped the scheme/nonce/ciphertext.
Once E2EE decryption ships, every message received before that point would
be permanently unreadable even though the ciphertext was there to decrypt.

Carry all three proto fields through: MessageContent.Encrypted is now a data
class with a ByteArray-aware equals/hashCode, ProtobufToLocal maps
Content.encrypted into it, and MessageContentSerialized.Encrypted persists
scheme/nonce/ciphertext hex-encoded (matching how this codebase already
hex-encodes other byte fields in ChatEntityMapper). Rendering is unchanged --
it still resolves to the unsupported-content tombstone.

Adds a proto -> domain -> serialized -> domain round trip test and a
proto/domain mapping test for Content.encrypted.
asContent() threw for MessageContent.Encrypted. Traced every caller:
ChatMessagingService.sendMessage/editMessage both call it on a full
MessageContent list built by the caller, including edit flows that
reassemble an existing message (e.g. rewriting a reply's inner text)
around content items the edit itself didn't touch. Retries and the
outbox resend the same content list rather than reconstructing it, so
they'd hit the same call. Any of those paths carrying a previously
received EncryptedContent message forward -- an edit or retry on a
message that happens to include one -- would crash instead of sending.

Now that MessageContent.Encrypted carries the original scheme/nonce/
ciphertext (previous commit), asContent() re-encodes it into
Content.encrypted using those fields, which is a faithful wire round-trip,
not new encryption. This keeps all callers unchanged and leaves the actual
encrypt path (composing new EncryptedContent) unimplemented, as before.
Same gap as EncryptedContent, found while auditing this round trip:
ChatMetadata.creator/useE2ee were added to the domain model and the
network mapper, but ChatEntityMapper never wrote them to
ChatMetadataEntity, so a chat rebuilt from Room after this session's
earlier work would silently lose the group creator and the transitional
E2EE flag on every reload.

Adds chat_metadata.creator_hex (nullable, hex-encoded like every other
user id column here) and use_e2ee (default 0), bumps the database to
version 38 with an AutoMigration, and wires both through
ChatEntityMapper's toEntity/toMetadata.
…rotocol to 0.12.0

Neither version is published yet, so this does not resolve until both
client packages are released.
@bmc08gt bmc08gt self-assigned this Sep 25, 2026
@github-actions github-actions Bot added area: network gRPC, connectivity, API, exchange rates type: feature New functionality area: build-system Gradle, convention plugins, build-logic labels Sep 25, 2026
@bmc08gt
bmc08gt marked this pull request as ready for review September 25, 2026 17:47
@bmc08gt
bmc08gt merged commit 0977e92 into code/cash Sep 25, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: build-system Gradle, convention plugins, build-logic area: network gRPC, connectivity, API, exchange rates type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant