macula_sealed_call (macula v13.2.0)

View Source

A sealed call (E2E seal scheme 1, design §5.1), one level above macula_seal: the caller seals a request to the provider's KEM key and opens the reply, and the provider opens the request and seals the reply.

Plaintext here is bytes. What a frame seals is the CBOR of its payload, which macula_frame writes and reads; this module neither knows nor cares, so a stream's STREAM_OPEN shares it.

A request is sealed under k_req with the fixed nonce 0^96, safe because a fresh encapsulation gives every request its own keys. A reply is sealed under k_rep with a fresh random nonce, carried in sealed, because one request can be answered more than once (a D25 retry across a provider restart).

Summary

Types

A sealed payload as a frame carries it.

What a provider holds: a lookup of its KEM private key and that key as carried by the key's id, and the id of the key it advertises now.

The reply's own fields the reply's AAD binds.

Functions

Whether Code is one a provider may answer a sealed request, a CALL or a STREAM_OPEN, with in the clear: an admission refusal, decided before anything is opened and carrying no application data. A caller refuses any other clear code on a sealed request as malformed.

Open a sealed reply to Request with the call's keys. A reply naming another key than the call's, or that does not open under this reply's frame type, request hash and provider, is sealed_refused.

Open a sealed request with the provider's key it names, and return the plaintext and the call's keys, which seal the reply. A request sealed to a key the provider does not hold, or that does not open, is refused naming the key the provider holds now, so the caller seals again to it.

The key a provider's clear sealed_refused names in its detail (or message, on a stream): the id it holds now, as 16 lowercase hex digits, or no_key when the detail is anything else, which a provider that holds no key sends.

Seal Plain as the reply to Request under the call's reply key, with a fresh random nonce.

Seal Plain as Request's payload to the provider's KEM key, and return the sealed payload and the call's keys, which open the reply.

Types

holder/0

-type holder() ::
          #{lookup := fun((<<_:64>>) -> {ok, macula_seal:private_key(), binary()} | error),
            current_key_id := <<_:64>>}.

A sealed payload as a frame carries it.

keys/0

-type keys() ::
          #{k_req := <<_:256>>,
            key_id := <<_:64>>,
            k_rep => <<_:256>>,
            k_c2p => <<_:256>>,
            k_p2c => <<_:256>>}.

What a provider holds: a lookup of its KEM private key and that key as carried by the key's id, and the id of the key it advertises now.

reply/0

-type reply() :: #{frame_type := binary(), request_hash := <<_:384>>, responded_by := <<_:256>>}.

sealed/0

-type sealed() ::
          #{scheme := 1, key_id := <<_:64>>, ct := binary(), kem_ct => binary(), nonce => <<_:96>>}.

The reply's own fields the reply's AAD binds.

Functions

clear_refusal(Code)

-spec clear_refusal(binary()) -> boolean().

Whether Code is one a provider may answer a sealed request, a CALL or a STREAM_OPEN, with in the clear: an admission refusal, decided before anything is opened and carrying no application data. A caller refuses any other clear code on a sealed request as malformed.

open_reply(Keys, Request, Reply, NotASealedReply)

-spec open_reply(keys(), macula_seal:request(), reply(), sealed()) ->
                    {ok, binary()} | {error, sealed_refused}.

Open a sealed reply to Request with the call's keys. A reply naming another key than the call's, or that does not open under this reply's frame type, request hash and provider, is sealed_refused.

open_request(Profile, _, Request, NotASealedRequest)

-spec open_request(macula_seal:profile(), holder(), macula_seal:request(), sealed()) ->
                      {ok, binary(), keys()} | {error, {sealed_refused, <<_:64>>}}.

Open a sealed request with the provider's key it names, and return the plaintext and the call's keys, which seal the reply. A request sealed to a key the provider does not hold, or that does not open, is refused naming the key the provider holds now, so the caller seals again to it.

refused_key(Detail)

-spec refused_key(term()) -> <<_:64>> | no_key.

The key a provider's clear sealed_refused names in its detail (or message, on a stream): the id it holds now, as 16 lowercase hex digits, or no_key when the detail is anything else, which a provider that holds no key sends.

seal_reply(_, Request, Reply, Plain)

-spec seal_reply(keys(), macula_seal:request(), reply(), binary()) -> sealed().

Seal Plain as the reply to Request under the call's reply key, with a fresh random nonce.

seal_request(Profile, Recipient, Request, Plain)

Seal Plain as Request's payload to the provider's KEM key, and return the sealed payload and the call's keys, which open the reply.