Skip to content

feat: add OMEMO 2 (urn:xmpp:omemo:2) support alongside 0.3.0#19

Merged
jcbrand merged 2 commits into
masterfrom
omemo-2-support
Jun 9, 2026
Merged

feat: add OMEMO 2 (urn:xmpp:omemo:2) support alongside 0.3.0#19
jcbrand merged 2 commits into
masterfrom
omemo-2-support

Conversation

@jcbrand

@jcbrand jcbrand commented Jun 9, 2026

Copy link
Copy Markdown
Member

Support both OMEMO versions, selected per device via a required version argument on SessionBuilder/SessionCipher/KeyHelper.generateSignedPreKey. Versions are identified by their XML namespace —
"eu.siacs.conversations.axolotl" (formerly 0.3.0) and "urn:xmpp:omemo:2".

The Double Ratchet and X3DH mechanics are shared; per-version differences live in a protocol "profile" (src/session/protocol-profile.ts): HKDF info strings, MAC length/associated data, the OMEMOMessage/OMEMOAuthenticatedMessage/ OMEMOKeyExchange wire format, and Ed25519<->Curve25519 identity-key handling.

  • native: add Curve25519<->Ed25519 public-key conversion wrappers, export them from the WASM build, and recompile. omemo:2 transfers the identity key in Ed25519 form while we keep Curve25519 internally; the Ed25519 key is derived from the public Curve25519 key via fe_montx_to_edy (sign bit 0), matching libomemo-c so the ik field and associated data interoperate.
  • protobufs: add OMEMO.proto + loadOMEMOMessages() (parsed with keepCase).
  • session: wire the profile through builder/cipher. The omemo:2 signed pre-key signature is computed over the raw 32-byte Curve25519 form.
  • record: persist per-session protocolVersion + omemo:2 associated data; bump storage schema to v2 with auto-migration of legacy sessions.
  • api: export getProtocolProfile, OMEMOVersion, and the conversion helpers; EncryptResult gains an optional kex flag.
  • tests: internal round-trips and full end-to-end sessions for both versions, plus a cross-implementation interop vector captured from libomemo-c (the library Dino uses) via tools/gen-omemo2-vector.c, where libomemo.js decrypts libomemo-c's actual omemo:2 messages.

BREAKING CHANGE: version is now a required SessionBuilder/SessionCipher constructor argument (no default). 0.3.0-only consumers pass "eu.siacs.conversations.axolotl".

@jcbrand jcbrand force-pushed the omemo-2-support branch 8 times, most recently from aaa87c1 to c148df0 Compare June 9, 2026 15:07
Support both OMEMO versions, selected per device via a required `version`
argument on SessionBuilder/SessionCipher/KeyHelper.generateSignedPreKey.
Versions are identified by their XML namespace —
"eu.siacs.conversations.axolotl" (formerly 0.3.0) and "urn:xmpp:omemo:2".

The Double Ratchet and X3DH mechanics are shared; per-version differences
live in a protocol "profile" (src/session/protocol-profile.ts): HKDF info
strings, MAC length/associated data, the OMEMOMessage/OMEMOAuthenticatedMessage/
OMEMOKeyExchange wire format, and Ed25519<->Curve25519 identity-key handling.

- native: add Curve25519<->Ed25519 public-key conversion wrappers, export
  them from the WASM build, and recompile. omemo:2 transfers the identity
  key in Ed25519 form while we keep Curve25519 internally; the Ed25519 key
  is derived from the public Curve25519 key via fe_montx_to_edy (sign bit 0),
  matching libomemo-c so the `ik` field and associated data interoperate.
- protobufs: add OMEMO.proto + loadOMEMOMessages() (parsed with keepCase).
- session: wire the profile through builder/cipher; per-version behavior
  (MAC form, registrationId-on-wire, Ed identity key) is carried as profile
  flags rather than version checks in the cipher. The omemo:2 signed pre-key
  signature is computed over the raw 32-byte Curve25519 form.
- record: persist per-session protocolVersion + omemo:2 associated data;
  store the remote identity key in both its internal Curve and published
  Ed25519 (omemo:2 trust) forms; bump storage schema to v2 with auto-migration
  of legacy sessions.
- api: export getProtocolProfile, OMEMOVersion, and the conversion helpers;
  EncryptResult gains an optional `kex` flag.
- tests: internal round-trips and full end-to-end sessions for both versions,
  plus cross-implementation interop with libomemo-c (the library Dino uses),
  captured with no build dependency at test time and pinned in both directions:
  libomemo.js decrypts libomemo-c's actual omemo:2 messages and verifies its
  signed-pre-key signature (tools/gen-omemo2-vector.c), and libomemo-c decrypts
  libomemo.js's own omemo:2 output (tools/dec-omemo2-vector.c, a manual
  one-time confirmation).

BREAKING CHANGE: `version` is now a required SessionBuilder/SessionCipher
constructor argument (no default). 0.3.0-only consumers pass
"eu.siacs.conversations.axolotl".
@jcbrand jcbrand force-pushed the omemo-2-support branch from c148df0 to fd2a54b Compare June 9, 2026 15:13
2.0.0 is the moment to lock the public API, so restrict it to what
downstream consumers reasonably need and stop exporting low-level
implementation details. Verified against the OMEMO 2 integration in
converse.js, which uses only SessionBuilder/SessionCipher/KeyHelper/
OMEMOAddress and curvePubKeyToEd25519PubKey at runtime (WebCrypto handles
its HKDF/AES/hashing).

Removed from the public API (still available internally):
- raw crypto primitives: encrypt, decrypt, sign, hash, HKDF, HKDFInternal,
  verifyMAC, ECDHE, getRandomBytes, createKeyPair, Ed25519Sign, Ed25519Verify
- internalCrypto and the Curve25519 class
- getProtocolProfile and the ProtocolProfile type (internal protocol machinery)

Kept: SessionBuilder, SessionCipher, KeyHelper, OMEMOAddress,
FingerprintGenerator, SessionRecord, InMemoryStore, startWorker/stopWorker,
util, the Ed25519<->Curve conversion helpers (needed for omemo:2 fingerprints),
and the contract types/enums.

BREAKING CHANGE: the low-level crypto primitives, internalCrypto, the
Curve25519 class, getProtocolProfile and ProtocolProfile are no longer
exported from the package entry point.
@jcbrand jcbrand merged commit 5c7ea95 into master Jun 9, 2026
5 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.

1 participant