Skip to content

feat: replace python-olm with fresholm for Matrix E2EE - #14139

Closed
cortexuvula wants to merge 1 commit into
NousResearch:mainfrom
cortexuvula:feat/fresholm-replace-python-olm
Closed

feat: replace python-olm with fresholm for Matrix E2EE#14139
cortexuvula wants to merge 1 commit into
NousResearch:mainfrom
cortexuvula:feat/fresholm-replace-python-olm

Conversation

@cortexuvula

Copy link
Copy Markdown

Summary

Replace python-olm (libolm C library) with fresholm (Rust/vodozemac-backed) for Matrix end-to-end encryption.

Changes

  • pyproject.toml: Replace mautrix[encryption] with mautrix>=0.20,<1 + fresholm>=0.2.4
  • gateway/platforms/matrix.py: Add import fresholm.import_hook before mautrix imports

Why fresholm over python-olm

python-olm fresholm
Implementation C (libolm) Rust (vodozemac)
Build Requires libolm-dev headers, CFFI compilation Pre-built wheels on PyPI
Maintenance libolm is deprecated by Matrix.org Actively maintained (successor)
Platform support Breaks frequently on macOS/Windows Universal wheels

The import hook intercepts import olm and transparently redirects to fresholm, so mautrix's crypto layer works unchanged.

Testing

Verified locally with Hermes gateway — Matrix/Beeper E2EE connects successfully:

  • import fresholm.import_hook; import olm loads correctly
  • Gateway: ✓ matrix connected with E2EE enabled
  • Message delivery to Beeper rooms working

This is a 2-file, 5-line change with no behavioral differences for users who don't install the matrix extra.

@alt-glitch alt-glitch added type/refactor Code restructuring, no behavior change P2 Medium — degraded but workaround exists platform/matrix Matrix adapter (E2EE) labels Apr 22, 2026
ayushere added a commit to ayushere/hermes-agent that referenced this pull request May 25, 2026
On macOS, `mautrix[encryption]` pulls in python-olm 3.2.16, which
bundles libolm source containing a C++ const-pointer bug in
`libolm/include/olm/list.hh`:

    T * const other_pos = other._data;
    // ...
    ++other_pos;   // error: cannot assign to const-qualified variable

Apple clang (Xcode 15+) rejects this as a hard compile error even
without -Werror, so `platform.matrix` lazy-install always fails on
macOS with:

    error: subprocess-exited-with-error
    × Getting requirements to build wheel did not run successfully.

Fix: when `ensure("platform.matrix")` runs on darwin, pre-install
python-olm from a patched copy of the sdist (one-line removal of the
spurious `const` qualifier) before the main pip install runs.
mautrix[encryption] then finds python-olm already satisfied and skips
the broken bundled-source build.

The patch is idempotent — if the target line is absent (upstream fixed
it), we log a warning and proceed without patching.

Relates to: NousResearch#27795 (adds libolm-dev for Docker, same root cause)
Relates to: NousResearch#14139 (replaces python-olm with fresholm long-term)
@tdussmann

Copy link
Copy Markdown

@cortexuvula Hmm... intriguing alternative for matrix dependencies snafu!
I have attempted the switch on my docker image, however the gateway will still complain and refuse to start:

ERROR gateway.platforms.matrix: Matrix: MATRIX_ENCRYPTION=true but E2EE dependencies are missing. Install with: pip install 'mautrix[encryption]' asyncpg aiosqlite
WARNING gateway.run: Matrix: mautrix not installed or credentials not set. Run: pip install 'mautrix[encryption]'                                                      
WARNING gateway.run: No adapter available for matrix 

Is this just the lazy-install-completion-check failing due to the switch of python-olm with fresholm or what?

@cortexuvula

Copy link
Copy Markdown
Author

Hey @tdussmann — good catch. The issue is that the PR updated the dependencies (pyproject.toml) and added the import hook (line 37), but did NOT update two downstream things:

  1. _check_e2ee_deps() (line 229-237) — still does from mautrix.crypto import OlmMachine which internally needs import olm. The fresholm import hook should intercept this, but only if import fresholm.import_hook at line 37 has already executed. If the Docker image is running old code (without that line), or if fresholm is not installed in the image, this check will fail.

  2. _E2EE_INSTALL_HINT (line 140-143) — still says pip install 'mautrix[encryption]' asyncpg aiosqlite which is the old python-olm path.

Can you confirm:

  • Is fresholm actually installed in your Docker image? (pip show fresholm)
  • Does your running code have the import fresholm.import_hook line at the top of matrix.py?

The error message showing the old hint (pip install 'mautrix[encryption]') suggests the Docker container might be running the old code. If you rebuilt with the PR branch and fresholm is installed, it should work — the import hook at line 37 activates before _check_e2ee_deps() runs at line 312.

That said, the PR is incomplete — I should also update _check_e2ee_deps() to explicitly try fresholm as a fallback and fix the hint messages. Let me push a follow-up commit.

@cortexuvula
cortexuvula requested a review from a team May 26, 2026 23:23
@tdussmann

Copy link
Copy Markdown

@cortexuvula

>>> from mautrix.crypto import OlmMachine
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    from mautrix.crypto import OlmMachine
  File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/__init__.py", line 1, in <module>
    from .account import OlmAccount
  File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/account.py", line 9, in <module>
    import olm
ModuleNotFoundError: No module named 'olm'
>>> import fresholm.import_hook
>>> from mautrix.crypto import OlmMachine
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    from mautrix.crypto import OlmMachine
  File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/__init__.py", line 1, in <module>
    from .account import OlmAccount
  File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/account.py", line 23, in <module>
    from .signature import sign_olm
  File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/signature.py", line 11, in <module>
    import unpaddedbase64
ModuleNotFoundError: No module named 'unpaddedbase64'

I think I got it: the problem stems from not requesting the mautrix[encryption] extra dependencies, which makes mautrix fail to init despite the import_hook of fresholm redirecting all python-olm imports.

See: https://github.com/mautrix/python/blob/master/setup.py#L5
here for the packages that would need to be included manually to make it work, yet exclude python-olm

@cortexuvula

Copy link
Copy Markdown
Author

You nailed it @tdussmann. mautrix[encryption] installs 4 packages:

  • python-olm → replaced by fresholm
  • unpaddedbase64was missing ← your error
  • pycryptodomewas missing
  • base58was missing

Pushed a fix (712dbfa80) to the PR branch:

  1. _E2EE_INSTALL_HINT — now includes all deps:
    pip install 'mautrix>=0.20' fresholm unpaddedbase64 pycryptodome base58 asyncpg aiosqlite

  2. _check_e2ee_deps() — now verifies unpaddedbase64, pycryptodome, and base58 are importable before declaring E2EE deps satisfied. This catches missing packages at startup with a clear error instead of crashing mid-operation when mautrix tries to import signature.py.

So the complete install for Docker users avoiding python-olm:

pip install 'mautrix>=0.20' fresholm unpaddedbase64 pycryptodome base58 asyncpg aiosqlite

Or for users who just want the mautrix[encryption] path (still works, just pulls in python-olm instead of fresholm):

pip install 'mautrix[encryption]' asyncpg aiosqlite

Replace python-olm (deprecated libolm C bindings) with fresholm
(Rust/vodozemac-backed) for Matrix end-to-end encryption.

Changes:
- pyproject.toml: Replace mautrix[encryption] with explicit deps
  (mautrix>=0.20, fresholm, unpaddedbase64, pycryptodome, base58)
- gateway/platforms/matrix.py:
  - Add fresholm import hook before mautrix imports
  - Update _E2EE_INSTALL_HINT to list all required packages
  - Update _check_e2ee_deps() to verify unpaddedbase64/pycryptodome/base58
    and fall back to explicit fresholm import hook activation

Why: python-olm requires libolm C headers + CFFI compilation which breaks
on macOS/Windows. fresholm ships pre-built wheels on PyPI.

Refs: #14139
@cortexuvula
cortexuvula force-pushed the feat/fresholm-replace-python-olm branch from 712dbfa to bcac6b7 Compare May 28, 2026 00:10
@cortexuvula

Copy link
Copy Markdown
Author

Updated the PR with a clean rebase on latest main. All three issues addressed:

  1. pyproject.toml — extra now explicitly lists all encryption deps:
    mautrix>=0.20,<1, fresholm>=0.2.4, unpaddedbase64, pycryptodome, base58 (plus existing Markdown/aiosqlite/asyncpg/aiohttp-socks)

  2. matrix.py — Fresholm import hook added at module top (line 43), before any mautrix imports

  3. _check_e2ee_deps() — Now checks for unpaddedbase64, pycryptodome, and base58 alongside OlmMachine, with a fresholm fallback

PR is mergeable but I don't have permissions to merge on NousResearch. @NousResearch can you merge when CI is green? 🙏

@tdussmann

Copy link
Copy Markdown

@cortexuvula I've just tried it out now, removing the installation of libolm-dev package beforehand, however I don't think the import_hook would work without making more changes to the way mautrix is being used...

2026-05-27T23:42:27.178521459Z ERROR mau.http: Failed to run handler
2026-05-27T23:42:27.178598415Z Traceback (most recent call last):
2026-05-27T23:42:27.178614959Z   File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/client/syncer.py", line 241, in _catch_errors
2026-05-27T23:42:27.178621909Z     await handler(data)
2026-05-27T23:42:27.178631138Z   File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/machine.py", line 228, in handle_to_device_event
2026-05-27T23:42:27.178642085Z     decrypted_evt = await self._decrypt_olm_event(evt)
2026-05-27T23:42:27.178648848Z                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-05-27T23:42:27.178654676Z   File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/decrypt_olm.py", line 42, in _decrypt_olm_event
2026-05-27T23:42:27.178660457Z     plaintext = await self._decrypt_olm_ciphertext(
2026-05-27T23:42:27.178666472Z                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-05-27T23:42:27.178672284Z         evt.sender, evt.content.sender_key, own_content
2026-05-27T23:42:27.178678639Z         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-05-27T23:42:27.178684796Z     )
2026-05-27T23:42:27.178692409Z     ^
2026-05-27T23:42:27.178697802Z   File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/decrypt_olm.py", line 88, in _decrypt_olm_ciphertext
2026-05-27T23:42:27.178739707Z     session = await self._create_inbound_session(sender_key, message.body)
2026-05-27T23:42:27.178746647Z               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-05-27T23:42:27.178751257Z   File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/decrypt_olm.py", line 128, in _create_inbound_session
2026-05-27T23:42:27.178755534Z     session = self.account.new_inbound_session(sender_key, ciphertext)
2026-05-27T23:42:27.178760083Z   File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/account.py", line 67, in new_inbound_session
2026-05-27T23:42:27.178769419Z     session = olm.InboundSession(self, olm.OlmPreKeyMessage(ciphertext), sender_key)
2026-05-27T23:42:27.178773029Z   File "/opt/hermes/.venv/lib/python3.13/site-packages/fresholm/compat/olm.py", line 348, in __init__
2026-05-27T23:42:27.178777236Z     temp = account.new_inbound_session(identity_key, message)
2026-05-27T23:42:27.178781406Z   File "/opt/hermes/.venv/lib/python3.13/site-packages/mautrix/crypto/account.py", line 67, in new_inbound_session
2026-05-27T23:42:27.178785355Z     session = olm.InboundSession(self, olm.OlmPreKeyMessage(ciphertext), sender_key)
2026-05-27T23:42:27.178789104Z                                        ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
2026-05-27T23:42:27.178792994Z   File "/opt/hermes/.venv/lib/python3.13/site-packages/fresholm/compat/olm.py", line 71, in __init__
2026-05-27T23:42:27.178796954Z     raise TypeError(f"ciphertext must be str or bytes, got {type(ciphertext)}")
2026-05-27T23:42:27.178800968Z TypeError: ciphertext must be str or bytes, got <class 'fresholm.compat.olm.OlmPreKeyMessage'>

Unfortunately though, since I've now wasted countless hours on trying to get matrix E2EE to work again, to no avail, I'm scratching the idea of having a matrix-channel entirely. Not even creating a fresh new bot-account for hermes was able to get it to work again with the current codebase and dependency versions...

mautrix.errors.crypto.DecryptionError: olm event doesn't contain ciphertext for this device

every single time... I suspect the extremely fractionalized ecosystem of Matrix clients/servers/SDKs to be the cause of all those recurring issues.

@cortexuvula cortexuvula closed this by deleting the head repository May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Medium — degraded but workaround exists platform/matrix Matrix adapter (E2EE) type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants