Packages

macula

3.15.2
7.1.0 7.0.0 6.0.0 5.2.2 5.2.1 5.2.0 5.1.0 5.0.0 4.8.0 4.7.1 4.7.0 4.6.0 4.5.0 4.4.10 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.1 4.3.0 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 4.1.0 4.0.0 3.16.0 3.15.3 3.15.2 3.15.1 3.14.0 3.13.0 3.12.1 3.12.0 3.11.1 3.11.0 3.10.3 3.10.2 3.10.1 3.9.0 3.8.0 3.7.0 3.5.0 3.4.0 3.3.0 3.2.0 3.1.0 3.0.0 2.1.1 2.1.0 2.0.0 1.5.2 1.5.1 1.4.30 1.4.29 1.4.28 1.4.27 1.4.26 1.4.25 1.4.24 1.4.23 1.4.22 1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.10 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.48.6 0.48.5 0.48.4 0.48.3 0.48.2 0.48.1 0.48.0 0.47.1 0.47.0 0.46.3 0.46.1 0.46.0 0.45.3 0.45.2 0.45.1 0.45.0 0.44.2 0.44.1 0.44.0 0.43.3 0.43.2 0.43.1 0.43.0 0.42.9 0.42.8 0.42.7 0.42.6 0.42.5 0.42.4 0.42.3 0.42.2 0.42.1 0.42.0 0.41.1 0.41.0 0.40.1 0.40.0 0.39.9 0.39.8 0.39.7 0.39.6 0.39.5 0.39.4 0.39.3 0.39.2 0.39.1 0.39.0 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.6 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.4 0.35.3 0.35.2 0.35.1 0.35.0 0.34.1 0.34.0 0.33.1 0.33.0 0.32.5 0.32.4 0.32.3 0.32.2 0.32.1 0.32.0 0.31.9 0.31.8 0.31.7 0.31.6 0.31.5 0.31.4 0.31.3 0.31.2 0.31.1 0.31.0 0.30.10 0.30.9 0.30.8 0.30.7 0.30.6 0.30.5 0.30.4 0.30.3 0.30.2 0.30.1 0.30.0 0.29.0 0.28.3 0.28.2 0.28.1 0.28.0 0.27.1 0.27.0 0.26.1 0.26.0 0.25.6 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.6 0.24.5 0.24.4 0.24.3 0.24.2 0.24.1 0.24.0 0.23.3 0.23.2 0.23.1 0.23.0 0.22.12 0.22.11 0.22.10 0.22.9 0.22.8 0.22.7 0.22.6 0.22.5 0.22.4 0.22.3 0.22.2 0.22.1 0.22.0 0.21.7 0.21.6 0.21.5 0.21.4 0.21.2 0.21.1 0.21.0 0.20.25 0.20.24 0.20.23 0.20.22 0.20.21 0.20.20 0.20.19 0.20.18 0.20.17 0.20.16 0.20.15 0.20.14 0.20.13 0.20.12 0.20.11 0.20.10 0.20.9 0.20.8 0.20.7 0.20.6 0.20.5 0.20.3 0.20.2 0.20.1 0.20.0 0.19.2 0.19.1 0.19.0 0.18.1 0.18.0 0.17.4 0.17.3 0.17.2 0.17.1 0.17.0 0.16.6 0.16.5 0.16.4 0.16.3 0.16.2 0.16.1 0.16.0 0.15.1 0.15.0 0.14.3 0.14.2 0.14.1 0.14.0 0.12.6 0.12.5 0.12.3 0.11.3 0.10.2 0.10.1 0.10.0 0.9.2 0.9.1 0.9.0 0.8.25 0.8.24 0.8.23 0.8.22 0.8.21 0.8.20 0.8.19 0.8.18 0.8.17 0.8.16 0.8.15 0.8.14 0.8.13 0.8.12 0.8.11 0.8.10 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.3 0.8.2 0.8.1 0.8.0 0.7.30 0.7.29 0.7.28 0.7.27 0.7.26 0.7.25 0.7.24 0.7.23 0.7.22 0.7.21 0.7.20 0.7.19 0.7.18 0.7.17 0.7.16 0.7.15 0.7.14 0.7.13 0.7.12 0.7.11 0.7.10 0.7.9 0.7.8 0.7.7 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.7 0.6.6 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.4 0.3.3 0.3.2 0.3.1

Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise

Current section

Files

Jump to
macula src derive_address macula_address.erl
Raw

src/derive_address/macula_address.erl

%%%-------------------------------------------------------------------
%%% @doc macula-net address derivation: pubkey -> IPv6.
%%%
%%% Per the macula-net spec (PLAN_MACULA_NET.md §3.1):
%%%
%%% <pre>
%%% addr = 0xfd | blake3(realm_master_pubkey)[0:40 bits]
%%% | blake3(identity_pubkey)[0:80 bits]
%%% </pre>
%%%
%%% Uses {@link macula_blake3_nif:hash/1} for the BLAKE3 primitive — no
%%% new NIF; reuses the SDK's existing crypto layer.
%%%
%%% == Realm-scoped identity ==
%%%
%%% An "identity" here is a realm-scoped Ed25519 keypair. A user/daemon
%%% in N realms holds N realm-scoped keypairs and gets N addresses (one
%%% per realm, all unlinkable at L3). See spec §3.6 for the full model.
%%% @end
%%%-------------------------------------------------------------------
-module(macula_address).
-export([
derive/2,
format/1,
derive_and_format/2
]).
-export_type([
pubkey/0,
ipv6_address/0,
ipv6_text/0
]).
-type pubkey() :: <<_:256>>. %% 32-byte Ed25519 pubkey
-type ipv6_address() :: <<_:128>>. %% 16-byte IPv6
-type ipv6_text() :: binary(). %% RFC 5952 lowercase
%% =============================================================================
%% Public API
%% =============================================================================
%% @doc Derive a macula-net IPv6 address from a realm + identity keypair.
-spec derive(RealmMasterPubkey :: pubkey(), IdentityPubkey :: pubkey()) ->
ipv6_address().
derive(RealmMasterPubkey, IdentityPubkey)
when is_binary(RealmMasterPubkey), byte_size(RealmMasterPubkey) =:= 32,
is_binary(IdentityPubkey), byte_size(IdentityPubkey) =:= 32 ->
<<RealmHashHead:5/binary, _/binary>> =
macula_blake3_nif:hash(RealmMasterPubkey),
<<IdHashHead:10/binary, _/binary>> =
macula_blake3_nif:hash(IdentityPubkey),
<<16#fd, RealmHashHead/binary, IdHashHead/binary>>.
%% @doc Render a 16-byte IPv6 binary as RFC 5952 lowercase text.
-spec format(ipv6_address()) -> ipv6_text().
format(<<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>) ->
Groups = [A, B, C, D, E, F, G, H],
iolist_to_binary(format_rfc5952(Groups)).
%% @doc Convenience: derive + format in one call.
-spec derive_and_format(pubkey(), pubkey()) -> ipv6_text().
derive_and_format(RealmMasterPubkey, IdentityPubkey) ->
format(derive(RealmMasterPubkey, IdentityPubkey)).
%% =============================================================================
%% RFC 5952 formatter
%% =============================================================================
%% Find the longest run of consecutive zero groups (length >= 2) and
%% replace it with '::'. The first zero of any tie wins per RFC 5952 §4.2.3.
format_rfc5952(Groups) ->
{Start, Len} = longest_zero_run(Groups),
case Len >= 2 of
true -> format_with_compression(Groups, Start, Len);
false -> hex_groups_joined(Groups, ":")
end.
format_with_compression(Groups, Start, Len) ->
{Head, Tail0} = lists:split(Start, Groups),
{_, Tail} = lists:split(Len, Tail0),
HeadStr = case Head of
[] -> "";
_ -> hex_groups_joined(Head, ":")
end,
TailStr = case Tail of
[] -> "";
_ -> hex_groups_joined(Tail, ":")
end,
[HeadStr, "::", TailStr].
hex_groups_joined(Groups, Sep) ->
lists:join(Sep, [io_lib:format("~.16b", [G]) || G <- Groups]).
%% Returns {StartIndex, RunLength} of the longest run of zeros.
longest_zero_run(Groups) ->
longest_zero_run(Groups, 0, 0, 0, 0, 0).
longest_zero_run([], _Idx, _CurStart, _CurLen, BestStart, BestLen) ->
{BestStart, BestLen};
longest_zero_run([0 | T], Idx, CurStart0, CurLen, BestStart, BestLen) ->
{CurStart, NewCurLen} =
case CurLen of
0 -> {Idx, 1};
_ -> {CurStart0, CurLen + 1}
end,
case NewCurLen > BestLen of
true -> longest_zero_run(T, Idx + 1, CurStart, NewCurLen, CurStart, NewCurLen);
false -> longest_zero_run(T, Idx + 1, CurStart, NewCurLen, BestStart, BestLen)
end;
longest_zero_run([_ | T], Idx, _CurStart, _CurLen, BestStart, BestLen) ->
longest_zero_run(T, Idx + 1, 0, 0, BestStart, BestLen).