hb_message.erl - Message Format Conversion & Manipulation
Overview
Purpose: Message format conversion and manipulation adapter
Module: hb_message
Core Format: TABM (Type Annotated Binary Messages)
Pattern: Any format → TABM → Any format
This module acts as an adapter between different message formats in HyperBEAM, providing conversion between AO-Core structured messages, Arweave transactions, ANS-104 data items, HTTP Signed Messages, and flat maps. Unless implementing a new codec, use hb_ao interfaces instead of this module directly.
Supported Formats
- Structured Messages: Richly typed AO-Core messages (
structured@1.0) - TABM: Type Annotated Binary Messages (internal format)
- ANS-104: Arweave data items (
ans104@1.0) - HTTP Signed: HTTP signature messages (
httpsig@1.0) - Flat Maps: Simple key-value maps (
flat@1.0)
TABM Format
Definition: Deep Erlang maps containing only binaries or other TABMs
Benefits:- Simple computational model (O(1) map access)
- Binary literals only (no types)
- Easy format conversion
- Efficient operations
Conversion Flow
Input Formats → TABM → Output Formats
Arweave TX/ANS-104 → dev_codec_ans104:from → TABM
HTTP Signed → dev_codec_httpsig:from → TABM
Flat Maps → dev_codec_flat:from → TABM
Structured → dev_codec_structured:from → TABM
TABM → dev_codec_ans104:to → Arweave TX/ANS-104
TABM → dev_codec_httpsig:to → HTTP Signed
TABM → dev_codec_structured:to → Structured
TABM → dev_codec_flat:to → Flat MapsDependencies
- HyperBEAM:
hb_ao,hb_util,hb_maps,hb_opts,hb_cache,hb_private - Codecs:
dev_codec_*modules - Arweave:
ar_wallet,ar_bundles - Includes:
include/hb.hrl
Public Functions Overview
%% Message ID
-spec id(Msg) -> ID.
-spec id(Msg, Committers) -> ID.
-spec id(Msg, Committers, Opts) -> ID.
%% Format Conversion
-spec convert(Msg, TargetFormat, Opts) -> ConvertedMsg.
-spec convert(Msg, TargetFormat, SourceFormat, Opts) -> ConvertedMsg.
%% Commitment Management
-spec uncommitted(Msg) -> UncommittedMsg.
-spec uncommitted(Msg, Opts) -> UncommittedMsg.
-spec committed(Msg, CommittersSpec, Opts) -> [Key].
-spec commit(Msg, Wallet) -> SignedMsg.
-spec commit(Msg, Wallet, Opts) -> SignedMsg.
-spec normalize_commitments(Msg, Opts) -> NormalizedMsg.
%% Verification
-spec verify(Msg) -> boolean().
-spec verify(Msg, Committers) -> boolean().
-spec verify(Msg, Committers, Opts) -> boolean().
%% Commitment Queries
-spec signers(Msg, Opts) -> [Address].
-spec commitment(IDOrSpec, Msg) -> Result.
-spec commitment(IDOrSpec, Msg, Opts) -> Result.
-spec commitments(Spec, Msg, Opts) -> #{CommID => Commitment}.
-spec commitment_devices(Msg, Opts) -> [Device].
-spec is_signed_key(Key, Msg, Opts) -> boolean().
%% Message Filtering
-spec with_only_committers(Msg, Committers) -> FilteredMsg.
-spec with_only_committers(Msg, Committers, Opts) -> FilteredMsg.
-spec with_only_committed(Msg, Opts) -> {ok, OnlyCommittedMsg} | {error, Reason}.
-spec without_unless_signed(Keys, Msg, Opts) -> FilteredMsg.
-spec with_commitments(Spec, Msg, Opts) -> MsgWithCommitments.
-spec without_commitments(Spec, Msg, Opts) -> MsgWithoutCommitments.
%% Message Comparison
-spec match(Map1, Map2) -> true | {mismatch, Type, Path, Val1, Val2}.
-spec match(Map1, Map2, Mode) -> true | {mismatch, Type, Path, Val1, Val2}.
-spec match(Map1, Map2, Mode, Opts) -> true | {mismatch, Type, Path, Val1, Val2}.
-spec diff(Msg1, Msg2, Opts) -> DiffMap.
%% Utilities
-spec type(Msg) -> tx | binary | deep | shallow.
-spec minimize(Msg) -> MinimizedMsg.
-spec find_target(Self, Req, Opts) -> {ok, TargetMsg}.
-spec default_tx_list() -> [{Key, DefaultValue}].
-spec filter_default_keys(Map) -> FilteredMap.
-spec print(Msg) -> ok.Public Functions
1. id/1, id/2, id/3
-spec id(Msg, Committers, Opts) -> ID
when
Msg :: map(),
Committers :: none | uncommitted | all | signed | [Device],
Opts :: map(),
ID :: binary().Description: Calculate message ID based on commitment spec. ID changes based on which parts are committed (signed).
Committer Options:none/uncommitted/unsigned- ID of raw message (no signatures)all/signed- ID including all signatures[Device1, Device2]- ID with specific commitment devices
-module(hb_message_id_test).
-include_lib("eunit/include/eunit.hrl").
unsigned_id_test() ->
Msg = #{<<"data">> => <<"value">>},
UnsignedID = hb_message:id(Msg, unsigned, #{}),
?assert(is_binary(UnsignedID)),
?assertEqual(43, byte_size(UnsignedID)). % Base64url encoded
signed_id_test() ->
Wallet = ar_wallet:new(),
Msg = #{<<"data">> => <<"value">>},
Signed = hb_message:commit(Msg, #{priv_wallet => Wallet}),
SignedID = hb_message:id(Signed, signed, #{}),
UnsignedID = hb_message:id(Signed, unsigned, #{}),
?assertNotEqual(SignedID, UnsignedID).
deterministic_id_test() ->
Msg = #{<<"key">> => <<"value">>},
ID1 = hb_message:id(Msg),
ID2 = hb_message:id(Msg),
?assertEqual(ID1, ID2).2. convert/3, convert/4
-spec convert(Msg, TargetFormat, SourceFormat, Opts) -> ConvertedMsg
when
Msg :: term(),
TargetFormat :: binary() | tabm,
SourceFormat :: binary() | tabm,
Opts :: map(),
ConvertedMsg :: term().Description: Convert message between formats via TABM intermediate representation.
Common Formats:<<"structured@1.0">>- AO-Core structured messages<<"ans104@1.0">>- ANS-104 data items<<"httpsig@1.0">>- HTTP signed messages<<"flat@1.0">>- Flat key-value mapstabm- TABM format (internal)
-module(hb_message_convert_test).
-include_lib("eunit/include/eunit.hrl").
structured_to_tabm_test() ->
Structured = #{<<"key">> => <<"value">>, <<"nested">> => #{<<"data">> => 123}},
TABM = hb_message:convert(Structured, tabm, <<"structured@1.0">>, #{}),
?assert(is_map(TABM)),
?assertEqual(<<"value">>, maps:get(<<"key">>, TABM)).
tabm_to_structured_test() ->
TABM = #{<<"key">> => <<"value">>},
Structured = hb_message:convert(TABM, <<"structured@1.0">>, tabm, #{}),
?assert(is_map(Structured)),
?assertEqual(<<"value">>, maps:get(<<"key">>, Structured)).
ans104_roundtrip_test() ->
Original = #{<<"data">> => <<"test">>},
ANS104 = hb_message:convert(Original, <<"ans104@1.0">>, #{}),
BackToStructured = hb_message:convert(ANS104, <<"structured@1.0">>, <<"ans104@1.0">>, #{}),
?assertEqual(<<"test">>, maps:get(<<"data">>, BackToStructured)).3. commit/2, commit/3
-spec commit(Msg, Wallet, Opts) -> SignedMsg
when
Msg :: map(),
Wallet :: {PrivKey, PubKey} | #{priv_wallet => {PrivKey, PubKey}},
Opts :: map(),
SignedMsg :: map().Description: Sign a message using provided wallet. Adds cryptographic commitments to message.
Test Code:-module(hb_message_commit_test).
-include_lib("eunit/include/eunit.hrl").
commit_message_test() ->
Wallet = ar_wallet:new(),
Msg = #{<<"data">> => <<"value">>},
Signed = hb_message:commit(Msg, #{priv_wallet => Wallet}),
?assert(maps:is_key(<<"commitments">>, Signed)),
?assert(hb_message:verify(Signed, all, #{})).
commit_with_device_test() ->
Wallet = ar_wallet:new(),
Msg = #{<<"data">> => <<"value">>},
Opts = #{<<"device">> => <<"ans104@1.0">>},
Signed = hb_message:commit(Msg, #{priv_wallet => Wallet}, Opts),
?assert(hb_message:verify(Signed)).4. verify/1, verify/2, verify/3
-spec verify(Msg, Committers, Opts) -> boolean()
when
Msg :: map(),
Committers :: all | [Device],
Opts :: map().Description: Verify cryptographic commitments in message. Returns true if all specified commitments are valid. Messages without commitments return true (nothing to fail).
-module(hb_message_verify_test).
-include_lib("eunit/include/eunit.hrl").
verify_signed_test() ->
Wallet = ar_wallet:new(),
Msg = hb_message:commit(#{<<"data">> => <<"value">>}, #{priv_wallet => Wallet}),
?assert(hb_message:verify(Msg, all, #{})).
verify_unsigned_test() ->
Msg = #{<<"data">> => <<"value">>},
%% Unsigned message has no commitments, so verify returns true (nothing to fail)
?assert(hb_message:verify(Msg, all, #{})),
%% But it has no signers
?assertEqual([], hb_message:signers(Msg, #{})).
verify_tampered_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"value">>}, #{priv_wallet => Wallet}),
Tampered = Signed#{<<"data">> => <<"modified">>},
?assertNot(hb_message:verify(Tampered, all, #{})).5. uncommitted/1, uncommitted/2
-spec uncommitted(Msg, Opts) -> UncommittedMsg
when
Msg :: map(),
Opts :: map(),
UncommittedMsg :: map().Description: Extract uncommitted (unsigned) portion of message, removing commitments.
Test Code:-module(hb_message_uncommitted_test).
-include_lib("eunit/include/eunit.hrl").
uncommitted_removes_sigs_test() ->
Wallet = ar_wallet:new(),
Original = #{<<"data">> => <<"value">>},
Signed = hb_message:commit(Original, #{priv_wallet => Wallet}),
Uncommitted = hb_message:uncommitted(Signed),
?assertNot(maps:is_key(<<"commitments">>, Uncommitted)),
?assertEqual(<<"value">>, maps:get(<<"data">>, Uncommitted)).6. committed/3
-spec committed(Msg, CommittersSpec, Opts) -> [binary()]
when
Msg :: map(),
CommittersSpec :: all | none | [CommitterID] | map(),
Opts :: map().Description: Return the list of keys that are covered by commitments.
Test Code:committed_keys_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
Keys = hb_message:committed(Signed, all, #{}),
?assert(is_list(Keys)),
?assert(lists:member(<<"data">>, Keys)).7. signers/2
-spec signers(Msg, Opts) -> [binary()]
when
Msg :: map(),
Opts :: map().Description: Return addresses of all committers with standard 256-bit addresses.
Test Code:signers_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
Signers = hb_message:signers(Signed, #{}),
?assert(is_list(Signers)),
?assert(length(Signers) >= 1).8. type/1
-spec type(Msg) -> tx | binary | deep | shallow
when
Msg :: term().Description: Determine the type of an encoded message.
Test Code:type_test() ->
?assertEqual(shallow, hb_message:type(#{<<"key">> => <<"value">>})),
?assertEqual(deep, hb_message:type(#{<<"nested">> => #{<<"key">> => <<"value">>}})),
?assertEqual(binary, hb_message:type(<<"raw">>)).9. minimize/1
-spec minimize(Msg) -> map()
when
Msg :: map().Description: Remove keys that can be regenerated (unsigned_id, content-digest) and private keys.
minimize_test() ->
Msg = #{<<"data">> => <<"test">>, <<"unsigned_id">> => <<"abc">>, <<"priv">> => #{}},
Min = hb_message:minimize(Msg),
?assert(maps:is_key(<<"data">>, Min)),
?assertNot(maps:is_key(<<"unsigned_id">>, Min)),
?assertNot(maps:is_key(<<"priv">>, Min)).10. normalize_commitments/2
-spec normalize_commitments(Msg, Opts) -> map()
when
Msg :: map(),
Opts :: map().Description: Ensure message has at least one unsigned ID present in commitments. Forces ID calculation work to happen strategically.
Test Code:normalize_commitments_test() ->
Msg = #{<<"data">> => <<"test">>},
Normalized = hb_message:normalize_commitments(Msg, #{}),
?assert(maps:is_key(<<"commitments">>, Normalized)).11. is_signed_key/3
-spec is_signed_key(Key, Msg, Opts) -> boolean()
when
Key :: binary(),
Msg :: map(),
Opts :: map().Description: Check if a specific key is covered by message commitments.
Test Code:is_signed_key_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
?assert(hb_message:is_signed_key(<<"data">>, Signed, #{})).12. commitment/2, commitment/3
-spec commitment(IDOrSpec, Msg, Opts) -> Commitment | not_found | {ok, CommID, Commitment} | multiple_matches
when
IDOrSpec :: binary() | map(),
Msg :: map(),
Opts :: map(),
Commitment :: map().Description: Extract a single commitment by ID or spec. When given an existing commitment ID, returns the commitment map directly. When given a spec, returns not_found, {ok, CommID, Commitment}, or multiple_matches. Use commitments/3 when expecting multiple matches.
commitment_by_id_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
Comms = maps:get(<<"commitments">>, Signed),
[FirstID | _] = maps:keys(Comms),
%% When given an existing ID, returns the commitment map directly
Result = hb_message:commitment(FirstID, Signed, #{}),
?assert(is_map(Result)),
?assert(maps:is_key(<<"signature">>, Result)).
commitment_by_spec_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
%% Use commitments/3 to get all matching commitments by device
Matches = hb_message:commitments(#{<<"commitment-device">> => <<"httpsig@1.0">>}, Signed, #{}),
?assert(map_size(Matches) >= 1).13. commitments/3
-spec commitments(Spec, Msg, Opts) -> #{CommID => Commitment}
when
Spec :: binary() | map(),
Msg :: map(),
Opts :: map().Description: Return all commitments matching a spec.
Test Code:commitments_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
%% Filter commitments by type
Matches = hb_message:commitments(#{<<"type">> => <<"rsa-pss-sha512">>}, Signed, #{}),
?assert(is_map(Matches)).14. commitment_devices/2
-spec commitment_devices(Msg, Opts) -> [binary()]
when
Msg :: map(),
Opts :: map().Description: Return the devices used to create commitments on a message.
Test Code:commitment_devices_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
Devices = hb_message:commitment_devices(Signed, #{}),
?assert(is_list(Devices)).15. with_only_committers/2, with_only_committers/3
-spec with_only_committers(Msg, Committers, Opts) -> map()
when
Msg :: map(),
Committers :: [binary()],
Opts :: map().Description: Filter message to include only specified committers' commitments.
Test Code:with_only_committers_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
Address = ar_wallet:to_address(Wallet),
Filtered = hb_message:with_only_committers(Signed, [Address]),
?assert(maps:is_key(<<"commitments">>, Filtered)).16. with_only_committed/2
-spec with_only_committed(Msg, Opts) -> {ok, map()} | {error, Reason}
when
Msg :: map(),
Opts :: map().Description: Return message with only keys that are covered by commitments. Must verify message separately.
Test Code:with_only_committed_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
{ok, Committed} = hb_message:with_only_committed(Signed, #{}),
?assert(maps:is_key(<<"data">>, Committed)).17. without_unless_signed/3
-spec without_unless_signed(Keys, Msg, Opts) -> map()
when
Keys :: binary() | [binary()],
Msg :: map(),
Opts :: map().Description: Remove specified keys unless they are signed.
Test Code:without_unless_signed_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
WithExtra = Signed#{<<"unsigned_key">> => <<"extra">>},
Filtered = hb_message:without_unless_signed([<<"unsigned_key">>], WithExtra, #{}),
?assertNot(maps:is_key(<<"unsigned_key">>, Filtered)).18. with_commitments/3
-spec with_commitments(Spec, Msg, Opts) -> map()
when
Spec :: binary() | [binary()] | map(),
Msg :: map(),
Opts :: map().Description: Filter message commitments to only those matching a spec.
Test Code:with_commitments_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
%% Get first commitment ID
Comms = maps:get(<<"commitments">>, Signed, #{}),
[FirstID | _] = maps:keys(Comms),
Filtered = hb_message:with_commitments([FirstID], Signed, #{}),
?assert(maps:is_key(<<"commitments">>, Filtered)).19. without_commitments/3
-spec without_commitments(Spec, Msg, Opts) -> map()
when
Spec :: binary() | [binary()] | map(),
Msg :: map(),
Opts :: map().Description: Remove commitments matching a spec from message.
Test Code:without_commitments_test() ->
Wallet = ar_wallet:new(),
Signed = hb_message:commit(#{<<"data">> => <<"test">>}, #{priv_wallet => Wallet}),
%% Get all commitment IDs and remove them
Comms = maps:get(<<"commitments">>, Signed, #{}),
CommIDs = maps:keys(Comms),
Filtered = hb_message:without_commitments(CommIDs, Signed, #{}),
RemainingComms = maps:get(<<"commitments">>, Filtered, #{}),
?assertEqual(0, map_size(RemainingComms)).20. match/2, match/3, match/4
-spec match(Map1, Map2, Mode, Opts) -> true | {mismatch, Type, Path, Val1, Val2}
when
Map1 :: map(),
Map2 :: map(),
Mode :: strict | only_present | primary,
Opts :: map().Description: Check if two messages match, with configurable matching modes.
Modes:strict- All keys in both maps must be present and matchonly_present- Only present keys in both maps must matchprimary- Only the primary map's keys must be present
match_strict_test() ->
Msg1 = #{<<"a">> => <<"1">>},
Msg2 = #{<<"a">> => <<"1">>},
?assertEqual(true, hb_message:match(Msg1, Msg2, strict, #{})).
match_mismatch_test() ->
Msg1 = #{<<"a">> => <<"1">>},
Msg2 = #{<<"a">> => <<"2">>},
?assertMatch({mismatch, _, _, _, _}, hb_message:match(Msg1, Msg2)).
match_only_present_test() ->
Msg1 = #{<<"a">> => <<"1">>},
Msg2 = #{<<"a">> => <<"1">>, <<"b">> => <<"2">>},
?assertEqual(true, hb_message:match(Msg1, Msg2, only_present, #{})).21. diff/3
-spec diff(Msg1, Msg2, Opts) -> map() | not_found
when
Msg1 :: map(),
Msg2 :: map(),
Opts :: map().Description: Return numeric differences between two messages, recursively comparing nested maps. Non-numeric changes return the new value. Keys only in first message are dropped.
Test Code:diff_numeric_test() ->
Msg1 = #{<<"count">> => 10},
Msg2 = #{<<"count">> => 15},
Diff = hb_message:diff(Msg1, Msg2, #{}),
?assertEqual(5, maps:get(<<"count">>, Diff)).
diff_new_key_test() ->
Msg1 = #{<<"a">> => <<"1">>},
Msg2 = #{<<"a">> => <<"1">>, <<"b">> => <<"new">>},
Diff = hb_message:diff(Msg1, Msg2, #{}),
?assertEqual(<<"new">>, maps:get(<<"b">>, Diff)).22. find_target/3
-spec find_target(Self, Req, Opts) -> {ok, TargetMsg}
when
Self :: map(),
Req :: map(),
Opts :: map().Description: Find the target message for an operation. Looks for target key in request; returns self if not present or if target is <<"self">>.
find_target_self_test() ->
Self = #{<<"data">> => <<"self">>},
Req = #{},
{ok, Target} = hb_message:find_target(Self, Req, #{}),
?assertEqual(Self, Target).
find_target_key_test() ->
Self = #{<<"data">> => <<"self">>},
Req = #{<<"target">> => <<"other">>, <<"other">> => #{<<"data">> => <<"other">>}},
{ok, Target} = hb_message:find_target(Self, Req, #{}),
?assertEqual(#{<<"data">> => <<"other">>}, Target).23. default_tx_list/0
-spec default_tx_list() -> [{binary(), term()}].Description: Return ordered list of #tx{} record fields with their default values as AO-Core keys.
default_tx_list_test() ->
List = hb_message:default_tx_list(),
?assert(is_list(List)),
?assert(length(List) > 0),
[{FirstKey, _} | _] = List,
?assert(is_binary(FirstKey)).24. filter_default_keys/1
-spec filter_default_keys(Map) -> map()
when
Map :: map().Description: Remove keys from a map that have default #tx{} record values.
filter_default_keys_test() ->
%% quantity = 0 is a default
Msg = #{<<"quantity">> => 0, <<"data">> => <<"test">>},
Filtered = hb_message:filter_default_keys(Msg),
?assertNot(maps:is_key(<<"quantity">>, Filtered)),
?assert(maps:is_key(<<"data">>, Filtered)).25. print/1
-spec print(Msg) -> ok
when
Msg :: map().Description: Pretty-print a message to standard error for debugging.
Test Code:print_test() ->
Msg = #{<<"data">> => <<"test">>},
?assertEqual(ok, hb_message:print(Msg)).Commitment System
Commitment Structure
#{
<<"data">> => <<"value">>,
<<"commitments">> => #{
<<"Device1">> => #{
<<"signature">> => Signature,
<<"public-key">> => PublicKey,
<<"commitment">> => SignedData
}
}
}Commitment Devices
Common Devices:<<"httpsig@1.0">>- HTTP signatures<<"ans104@1.0">>- ANS-104 signatures- Custom devices can add commitments
Common Patterns
%% Convert to TABM for processing
TABM = hb_message:convert(Msg, tabm, #{}),
%% Convert between formats
ANS104 = hb_message:convert(Structured, <<"ans104@1.0">>, #{}),
%% Sign message
Wallet = ar_wallet:new(),
Signed = hb_message:commit(Msg, #{priv_wallet => Wallet}),
%% Verify signature
IsValid = hb_message:verify(Signed, all, #{}),
%% Get message ID
UnsignedID = hb_message:id(Msg, unsigned, #{}),
SignedID = hb_message:id(Msg, signed, #{}),
%% Extract unsigned portion
Original = hb_message:uncommitted(SignedMsg),
%% Get signers
Signers = hb_message:signers(SignedMsg, #{}),
%% Filter to committed keys only
Committed = hb_message:with_only_committed(Msg, #{}),
%% Check message type
Type = hb_message:type(Msg), % structured | ans104 | httpsig | flat
%% Minimize message (remove defaults)
Minimal = hb_message:minimize(Msg),
%% Print for debugging
hb_message:print(Msg).Message Type Detection
type(Msg) ->
case {is_record(Msg, tx), is_map(Msg)} of
{true, _} -> ans104;
{_, true} ->
case maps:is_key(<<"commitments">>, Msg) of
true -> httpsig;
false ->
case has_type_annotations(Msg) of
true -> structured;
false -> flat
end
end;
_ -> unknown
end.References
- Codecs -
dev_codec_*.erlmodules - AO Core -
hb_ao.erl - Cache -
hb_cache.erl - Test Vectors -
hb_message_test_vectors.erl - Arweave -
ar_wallet.erl,ar_bundles.erl
Notes
- TABM Central: All conversions go through TABM format
- Codec System: Each format has codec module (
dev_codec_*) - ID Calculation: Depends on commitment specification
- Signature Support: Multiple commitment devices per message
- Priv Preservation: Private data preserved across conversions
- Two-Phase Convert: Input→TABM, TABM→Output
- Verification: Check all or specific commitment devices
- Uncommitted: Extract original data without signatures
- Type Detection: Automatic format detection
- Minimize: Remove default/unnecessary fields
- Test Vectors: Comprehensive tests in separate module
- Use hb_ao: Prefer
hb_aofor normal message operations - Low-level API: Direct use rare, mainly for codec authors
- Format Agnostic: Works with any registered codec
- Commitment Devices: Extensible signature system