Page MenuHomePhorge

From libolm to vodozemac
Updated 70 Days AgoPublic

Please do not hesitate to update, polish, enrich or correct this document if you see fit.

This document aims to provide you an overview of how to port your Matrix application from libolm to vodozemac.

Definitions

We will be using the following definitions in the document:

  • The E2EE library means either libolm or vodozemac.
  • The application means any code that uses the E2EE library, which may be another library.
  • The user means anyone who interacts with the application in any way other than writing code.

Differences

vodozemac-bindings has a C++ public API, as opposed to the C API of libolm. There are a couple of differences in the API:

  • Memory allocations are managed by vodozemac, not by the application.
  • You can no longer pass random data into the E2EE library, as vodozemac embeds the rust's random generator in it. This renders the "test vectors" in the spec effectively useless.
  • You have to have exceptions support in your compiler. This is a limitation of cxx.rs , which is used to generate the bindings. See https://github.com/dtolnay/cxx/issues/1052 for more information. This might change later. You no longer need to have exceptions support to use this binding. See below for more details.
  • Pickle keys are now 32 bytes, instead of any string.

Overview of vodozemac-bindings

Everything is in the vodozemac namespace. A function may throw an exception if it returns Result<T> in rust code. In the generated C++ header, it corresponds to the presence or absence of the noexcept specifier. If a function is declared noexcept, it does not throw an exception; otherwise, it may.

You can use the following code to check for exceptions:

template<class Func>
auto checkVodozemacError(Func &&func) -> std::optional<std::invoke_result_t<Func>>
{
    try {
        return std::forward<Func>(func)();
    } catch (const std::exception &e) {
        return std::nullopt;
    }
}

With https://lily-is.land/kazv/vodozemac-bindings/-/merge_requests/6 , every vodozemac function that may throw has a noexcept counterpart, with the name FUNC_NAME_noexcept where FUNC_NAME is the original function name. If you wish to use no exceptions in your code, you can use the noexcept functions instead. These functions will return a Box<Maybe<ValueType>> where ValueType is the original return type. The Maybe types have the following signature (due to cxx restrictions, they are actually not template types in C++):

template<class T>
struct Maybe<T> {
    /// return true iff the Maybe is valid
    bool is_valid() const noexcept;

    // **the following functions can only be used if the Maybe is valid.**

    /// return true iff the Maybe contains a value.
    bool has_value() const noexcept;
    /// return the contained value. can only be used if the Maybe is valid and contains a value.
    /// **the Maybe will become invalid after calling.**
    ::rust::Box<T> take_value() noexcept;
    /// return the contained error as a rust String. can only be used if the Maybe is valid and does not contain a value.
    /// **the Maybe will become invalid after calling.**
    ::rust::String take_error() noexcept;

    ~Maybe() = delete;
};

You can use it like this:

// result is a Box<Maybe<T>>
auto result = FUNC_NAME_noexcept();
if (result->has_value()) {
    auto value = result->take_value();
    // from now on, result cannot be used again (except the is_valid() method).
    // deal with value
} else {
    auto error = result->take_error();
    // from now on, result cannot be used again (except the is_valid() method).
    // deal with error
}

Converting from libolm

vodozemac-bindings provides function to convert data from libolm pickle format. They are called *_from_libolm_pickle and takes two arguments: the first one is the pickle data, as rust::Str, the other being the pickle key, as rust::Slice<const unsigned char>. rust::Str can be directly converted from a std::string, while rust::Slice needs some work, for example:

auto session = checkVodozemacError([&]() {
    return vodozemac::megolm::inbound_group_session_from_libolm_pickle(pickleData, rust::Slice<const unsigned char>(pickleKey.data(), pickleKey.size()));
});

Changes to first inbound olm message

One important change in vodozemac is that when you create an inbound olm session from an encrypted message, you instantly get the decrypted content as return, and decrypt() will NOT work for the first message.

auto res = checkVodozemacError([&]() {
    auto identityKey = vodozemac::types::curve_key_from_base64(rust::Str(theirIdentityKey));
    auto msg = vodozemac::olm::olm_message_from_parts(vodozemac::olm::OlmMessageParts{
            0, // pre-key message
            message,
        });
    return account->create_inbound_session(
        *identityKey,
        *msg
    );
});
if (res.has_value()) {
    session = std::move(res->session);
    firstDecrypted = std::string(res->plaintext.begin(), res->plaintext.end());
}

Sample code

The commits referenced in https://iron.lily-is.land/T125 provides a sample code to migrate from libolm to vodozemac.

Last Author
tusooa
Last Edited
Sep 23 2024, 6:15 PM

Event Timeline

tusooa published a new version of this document.
tusooa edited the content of this document. (Show Details)
tusooa published a new version of this document.