Skip to content

Commit 4f95a84

Browse files
committed
fix: add bundle tags to ans104 and tx commitments
1 parent ab8dfec commit 4f95a84

File tree

10 files changed

+467
-205
lines changed

10 files changed

+467
-205
lines changed

src/ar_bundles.erl

Lines changed: 11 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
-module(ar_bundles).
2-
-export([signer/1, is_signed/1]).
3-
-export([id/1, id/2, hd/1, member/2, find/2, tagfind/3]).
2+
-export([signer/1]).
3+
-export([id/1, id/2, hd/1, member/2, find/2]).
44
-export([new_item/4, sign_item/2, verify_item/1]).
55
-export([encode_tags/1, decode_tags/1]).
6-
-export([serialize/1, deserialize/1, serialize_data/1, serialize_bundle/2]).
6+
-export([serialize/1, deserialize/1, serialize_bundle/2]).
77
-export([data_item_signature_data/1]).
8-
-export([type/1]).
98
-include("include/hb.hrl").
109
-include_lib("eunit/include/eunit.hrl").
1110

@@ -19,10 +18,6 @@
1918
signer(#tx { owner = ?DEFAULT_OWNER }) -> undefined;
2019
signer(Item) -> crypto:hash(sha256, Item#tx.owner).
2120

22-
%% @doc Check if an item is signed.
23-
is_signed(Item) ->
24-
Item#tx.signature =/= ?DEFAULT_SIG.
25-
2621
%% @doc Return the ID of an item -- either signed or unsigned as specified.
2722
%% If the item is unsigned and the user requests the signed ID, we return
2823
%% the atom `not_signed'. In all other cases, we return the ID of the item.
@@ -76,17 +71,6 @@ find(Key, Item = #tx { data = Data }) ->
7671
find(_Key, _) ->
7772
not_found.
7873

79-
%% @doc Case-insensitively find a tag in a list and return its value.
80-
tagfind(Key, Tags, Default) ->
81-
LowerCaseKey = hb_util:to_lower(Key),
82-
Found = lists:search(fun({TagName, _}) ->
83-
hb_util:to_lower(TagName) == LowerCaseKey
84-
end, Tags),
85-
case Found of
86-
{value, {_TagName, Value}} -> Value;
87-
false -> Default
88-
end.
89-
9074
%% @doc Create a new data item. Should only be used for testing.
9175
new_item(Target, Anchor, Tags, Data) ->
9276
dev_arweave_common:reset_ids(
@@ -196,7 +180,7 @@ enforce_valid_tx(TX) ->
196180
%% @doc Generate the data segment to be signed for a data item.
197181
data_item_signature_data(RawItem) ->
198182
true = enforce_valid_tx(RawItem),
199-
Item = serialize_data(RawItem),
183+
Item = dev_arweave_common:serialize_data(RawItem),
200184
ar_deep_hash:hash([
201185
utf8_encoded("dataitem"),
202186
utf8_encoded("1"),
@@ -237,7 +221,7 @@ serialize(not_found) -> throw(not_found);
237221
serialize(TX) when is_binary(TX) -> TX;
238222
serialize(RawTX) when is_record(RawTX, tx) ->
239223
true = enforce_valid_tx(RawTX),
240-
TX = serialize_data(RawTX),
224+
TX = dev_arweave_common:serialize_data(RawTX),
241225
EncodedTags = encode_tags(TX#tx.tags),
242226
<<
243227
(encode_signature_type(TX#tx.signature_type))/binary,
@@ -252,27 +236,6 @@ serialize(RawTX) when is_record(RawTX, tx) ->
252236
serialize(TX) ->
253237
throw({cannot_serialize_tx, must_be_binary_or_tx, TX}).
254238

255-
serialize_data(Item = #tx{data = Data}) when is_binary(Data) ->
256-
Item;
257-
serialize_data(Item = #tx{data = Data}) ->
258-
IsBundleMap = type(Item) == map,
259-
?event({serialize_data,
260-
hb_util:human_id(Item#tx.unsigned_id), hb_util:human_id(Item#tx.id),
261-
{is_bundle_map, IsBundleMap},
262-
{is_list, is_list(Data)},
263-
{is_map, is_map(Data)}}),
264-
ConvertedData =
265-
case {IsBundleMap, is_list(Data), is_map(Data)} of
266-
{true, true, false} ->
267-
dev_arweave_common:convert_bundle_list_to_map(Data);
268-
{false, false, true} ->
269-
dev_arweave_common:convert_bundle_map_to_list(Data);
270-
_ ->
271-
Data
272-
end,
273-
{Manifest, SerializedData} = serialize_bundle(ConvertedData, false),
274-
Item#tx{data = SerializedData, manifest = Manifest}.
275-
276239
serialize_bundle(List, Normalize) when is_list(List) ->
277240
FinalizedData = finalize_bundle_data(
278241
lists:map(
@@ -303,7 +266,7 @@ serialize_bundle(Map, Normalize) when is_map(Map) ->
303266
[{NewManifestUnsignedID, NewManifestSerialized} | maps:values(BinItems)]),
304267
{NewManifest, FinalizedData};
305268
serialize_bundle(Data, _Normalize) when is_binary(Data) ->
306-
Data;
269+
{undefined, Data};
307270
serialize_bundle(Data, _Normalize) ->
308271
throw({cannot_serialize_tx_data, must_be_list_or_map, Data}).
309272

@@ -454,21 +417,8 @@ deserialize_item(Binary) ->
454417
})
455418
).
456419

457-
type(Item) ->
458-
Format = tagfind(<<"bundle-format">>, Item#tx.tags, <<>>),
459-
Version = tagfind(<<"bundle-version">>, Item#tx.tags, <<>>),
460-
MapTXID = tagfind(<<"bundle-map">>, Item#tx.tags, <<>>),
461-
case {hb_util:to_lower(Format), hb_util:to_lower(Version), MapTXID} of
462-
{<<"binary">>, <<"2.0.0">>, <<>>} ->
463-
list;
464-
{<<"binary">>, <<"2.0.0">>, _} ->
465-
map;
466-
_ ->
467-
binary
468-
end.
469-
470420
maybe_unbundle(Item) ->
471-
case type(Item) of
421+
case dev_arweave_common:type(Item) of
472422
list -> unbundle_list(Item);
473423
binary -> Item;
474424
map -> unbundle_map(Item)
@@ -481,7 +431,7 @@ unbundle_list(Item) ->
481431
end.
482432

483433
unbundle_map(Item) ->
484-
MapTXID = tagfind(<<"bundle-map">>, Item#tx.tags, <<>>),
434+
MapTXID = dev_arweave_common:tagfind(<<"bundle-map">>, Item#tx.tags, <<>>),
485435
case unbundle(Item#tx.data) of
486436
detached -> Item#tx{data = detached};
487437
Items ->
@@ -921,7 +871,7 @@ arbundles_list_bundle_roundtrip_test() ->
921871
?assert(verify_item(Item2)),
922872
?assert(verify_item(Item3)),
923873

924-
Reserialized = serialize_data(Deserialized),
874+
Reserialized = dev_arweave_common:normalize(Deserialized),
925875
?event(debug_test, {reserialized, Reserialized}),
926876
?assert(verify_item(Reserialized)),
927877
?assertEqual(Bin, Reserialized#tx.data),
@@ -952,7 +902,7 @@ arbundles_single_list_bundle_roundtrip_test() ->
952902
?assertEqual([{<<"Type">>, <<"list">>}, {<<"Index">>, <<"1">>}], Item#tx.tags),
953903
?assert(verify_item(Item)),
954904

955-
Reserialized = serialize_data(Deserialized),
905+
Reserialized = dev_arweave_common:normalize(Deserialized),
956906
?event(debug_test, {reserialized, Reserialized}),
957907
?assert(verify_item(Reserialized)),
958908
?assertEqual(Bin, Reserialized#tx.data),
@@ -984,7 +934,7 @@ arbundles_map_bundle_roundtrip_test() ->
984934
Manifest = Deserialized#tx.manifest,
985935
?event(debug_test, {manifest, Manifest}),
986936
?assertNotEqual(undefined, Manifest),
987-
?assertEqual(false, is_signed(Manifest)),
937+
?assertEqual(false, dev_arweave_common:is_signed(Manifest)),
988938
?assertEqual([
989939
{<<"data-protocol">>, <<"bundle-map">>},
990940
{<<"variant">>, <<"0.0.1">>}

src/ar_format.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ format(TX, Indent, Opts) when is_record(TX, tx) ->
5959
format_line("!!! CAUTION: ITEM IS SIGNED BUT INVALID !!!", Indent + 1);
6060
false -> []
6161
end ++
62-
case ar_bundles:is_signed(TX) of
62+
case dev_arweave_common:is_signed(TX) of
6363
true ->
6464
format_line("Signer: ~s",
6565
[hb_util:safe_encode(ar_bundles:signer(TX))],

src/ar_tx.erl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@ json_struct_to_tx(TXStruct) ->
237237
Xs ->
238238
Xs
239239
end,
240-
?event(debug_test, {json_struct_to_tx, {tags, {explicit, Tags}}}),
241240
Data = hb_util:decode(hb_util:find_value(<<"data">>, TXStruct)),
242241
Format =
243242
case hb_util:find_value(<<"format">>, TXStruct) of

src/dev_arweave.erl

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -306,12 +306,59 @@ post_ans104_tx_test() ->
306306
GetRes
307307
),
308308
ok.
309-
get_tx_test() ->
309+
get_tx_basic_data_test() ->
310310
Node = hb_http_server:start_node(),
311-
Path = <<"/~a[email protected]/tx?tx=MH1M9gedlPsu7-57-N0S_-F1OBoYacLYLe5jL0Tvr9c">>,
312-
{ok, StructuredTX} = hb_http:get(Node, Path, #{}),
313-
?event(debug_test, {structured_tx, StructuredTX}),
314-
?assert(hb_message:verify(StructuredTX, all, #{})).
311+
Path = <<"/~a[email protected]/tx?tx=ptBC0UwDmrUTBQX3MqZ1lB57ex20ygwzkjjCrQjIx3o">>,
312+
{ok, Structured} = hb_http:get(Node, Path, #{}),
313+
?event(debug_test, {structured_tx, Structured}),
314+
?assert(hb_message:verify(Structured, all, #{})),
315+
% Hash the data to make it easier to match
316+
StructuredWithHash = Structured#{
317+
<<"data">> => hb_util:encode(
318+
crypto:hash(sha256, (maps:get(<<"data">>, Structured)))
319+
)
320+
},
321+
ExpectedMsg = #{
322+
<<"data">> => <<"PEShWA1ER2jq7CatAPpOZ30TeLrjOSpaf_Po7_hKPo4">>,
323+
<<"reward">> => <<"482143296">>,
324+
<<"anchor">> => <<"XTzaU2_m_hRYDLiXkcleOC4zf5MVTXIeFWBOsJSRrtEZ8kM6Oz7EKLhZY7fTAvKq">>,
325+
<<"content-type">> => <<"application/json">>
326+
},
327+
?assert(hb_message:match(ExpectedMsg, StructuredWithHash, only_present)),
328+
ok.
329+
330+
get_tx_rsa_nested_bundle_test() ->
331+
Node = hb_http_server:start_node(),
332+
Path = <<"/~a[email protected]/tx?tx=bndIwac23-s0K11TLC1N7z472sLGAkiOdhds87ZywoE">>,
333+
{ok, Structured} = hb_http:get(Node, Path, #{}),
334+
TABM = hb_message:convert(
335+
Structured,
336+
tabm,
337+
#{
338+
<<"device">> => <<"[email protected]">>,
339+
<<"bundle">> => true
340+
},
341+
#{}),
342+
{ok, TX} = dev_codec_tx:to(TABM, #{}, #{}),
343+
?event(debug_test, {tabm, TABM}),
344+
?event(debug_test, {tx, TX}),
345+
% ?event(debug_test, {structured_tx, Structured}),
346+
?assert(ar_tx:verify(TX)),
347+
?assert(hb_message:verify(Structured, all, #{})),
348+
% % Hash the data to make it easier to match
349+
% StructuredWithHash = Structured#{
350+
% <<"data">> => hb_util:encode(
351+
% crypto:hash(sha256, (maps:get(<<"data">>, Structured)))
352+
% )
353+
% },
354+
% ExpectedMsg = #{
355+
% <<"data">> => <<"PEShWA1ER2jq7CatAPpOZ30TeLrjOSpaf_Po7_hKPo4">>,
356+
% <<"reward">> => <<"482143296">>,
357+
% <<"anchor">> => <<"XTzaU2_m_hRYDLiXkcleOC4zf5MVTXIeFWBOsJSRrtEZ8kM6Oz7EKLhZY7fTAvKq">>,
358+
% <<"content-type">> => <<"application/json">>
359+
% },
360+
% ?assert(hb_message:match(ExpectedMsg, StructuredWithHash, only_present)),
361+
ok.
315362

316363

317364
get_bad_tx_test() ->

src/dev_arweave_common.erl

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,40 @@
11
%%% @doc Utility module for routing functionality to ar_bundles.erl or
22
%%% ar_tx.erl based off #tx.format.
33
-module(dev_arweave_common).
4-
-export([reset_ids/1, generate_id/2, normalize/1]).
4+
-export([is_signed/1, type/1, tagfind/3]).
5+
-export([reset_ids/1, generate_id/2, normalize/1, serialize_data/1]).
56
-export([convert_bundle_list_to_map/1, convert_bundle_map_to_list/1]).
67
-include("include/hb.hrl").
78
-include_lib("eunit/include/eunit.hrl").
89

10+
%% @doc Check if an item is signed.
11+
is_signed(TX) ->
12+
TX#tx.signature =/= ?DEFAULT_SIG.
13+
14+
type(Item) ->
15+
Format = tagfind(<<"bundle-format">>, Item#tx.tags, <<>>),
16+
Version = tagfind(<<"bundle-version">>, Item#tx.tags, <<>>),
17+
MapTXID = tagfind(<<"bundle-map">>, Item#tx.tags, <<>>),
18+
case {hb_util:to_lower(Format), hb_util:to_lower(Version), MapTXID} of
19+
{<<"binary">>, <<"2.0.0">>, <<>>} ->
20+
list;
21+
{<<"binary">>, <<"2.0.0">>, _} ->
22+
map;
23+
_ ->
24+
binary
25+
end.
26+
27+
%% @doc Case-insensitively find a tag in a list and return its value.
28+
tagfind(Key, Tags, Default) ->
29+
LowerCaseKey = hb_util:to_lower(Key),
30+
Found = lists:search(fun({TagName, _}) ->
31+
hb_util:to_lower(TagName) == LowerCaseKey
32+
end, Tags),
33+
case Found of
34+
{value, {_TagName, Value}} -> Value;
35+
false -> Default
36+
end.
37+
938
%% @doc Re-calculate both of the IDs for a #tx. This is a wrapper
1039
%% function around `update_ids/1' that ensures both IDs are set from
1140
%% scratch.
@@ -47,33 +76,55 @@ generate_signature_data_segment(TX) ->
4776
%% @doc Ensure that a data item (potentially containing a map or list) has a
4877
%% standard, serialized form.
4978
normalize(not_found) -> throw(not_found);
50-
normalize(Item = #tx{data = Bin}) when is_binary(Bin) ->
79+
normalize(TX = #tx{data = Bin}) when is_binary(Bin) ->
5180
?event({normalize, binary,
52-
hb_util:human_id(Item#tx.unsigned_id), hb_util:human_id(Item#tx.id)}),
81+
hb_util:human_id(TX#tx.unsigned_id), hb_util:human_id(TX#tx.id)}),
5382
reset_ids(
5483
normalize_data_root(
5584
normalize_data_size(
5685
reset_owner_address(
57-
Item))));
86+
TX))));
5887
normalize(Bundle) when is_list(Bundle); is_map(Bundle) ->
5988
?event({normalize, bundle}),
6089
normalize(#tx{ data = Bundle });
61-
normalize(Item = #tx { data = Data }) when is_list(Data) ->
62-
?event({normalize, list,
63-
hb_util:human_id(Item#tx.unsigned_id), hb_util:human_id(Item#tx.id)}),
64-
normalize(Item#tx{data = convert_bundle_list_to_map(Data)});
65-
normalize(Item = #tx{data = Data}) when is_map(Data) ->
66-
{Manifest, Bin} = ar_bundles:serialize_bundle(Item#tx.data, true),
67-
SerializedItem =
68-
Item#tx{
69-
data = Bin,
70-
manifest = Manifest,
71-
tags = add_manifest_tags(
72-
add_bundle_tags(Item#tx.tags),
73-
ar_bundles:id(Manifest, unsigned)
74-
)
75-
},
76-
normalize(SerializedItem).
90+
normalize(TX = #tx{data = Data}) ->
91+
SerializedTX = serialize_data(TX, true),
92+
NormalizedTX = case is_signed(TX) of
93+
true ->
94+
SerializedTX;
95+
false ->
96+
add_bundle_tags(SerializedTX)
97+
end,
98+
normalize(NormalizedTX).
99+
100+
serialize_data(TX) -> serialize_data(TX, false).
101+
serialize_data(Item = #tx{data = Data}, _) when is_binary(Data) ->
102+
Item;
103+
serialize_data(Item = #tx{data = Data}, NormalizeChildren) ->
104+
IsBundleMap = type(Item) == map,
105+
?event({serialize_data,
106+
hb_util:human_id(Item#tx.unsigned_id), hb_util:human_id(Item#tx.id),
107+
{normalize_children, NormalizeChildren},
108+
{is_bundle_map, IsBundleMap},
109+
{is_list, is_list(Data)},
110+
{is_map, is_map(Data)}}),
111+
ConvertedData =
112+
case {is_signed(Item), IsBundleMap, is_list(Data), is_map(Data)} of
113+
{true, true, true, false} ->
114+
% Signed transaction with bundle-map tag and list data
115+
convert_bundle_list_to_map(Data);
116+
{true, false, false, true} ->
117+
% Signed transaction without bundle-map tag and map data
118+
convert_bundle_map_to_list(Data);
119+
{false, _, true, false} ->
120+
% Unsigned transaction with list data
121+
convert_bundle_list_to_map(Data);
122+
_ ->
123+
Data
124+
end,
125+
{Manifest, SerializedData} =
126+
ar_bundles:serialize_bundle(ConvertedData, NormalizeChildren),
127+
Item#tx{data = SerializedData, manifest = Manifest}.
77128

78129
convert_bundle_list_to_map(Data) ->
79130
maps:from_list(
@@ -97,15 +148,16 @@ convert_bundle_map_to_list(Data) ->
97148
lists:seq(1, maps:size(Data))
98149
).
99150

100-
add_bundle_tags(Tags) -> ?BUNDLE_TAGS ++ (Tags -- ?BUNDLE_TAGS).
101-
102-
add_manifest_tags(Tags, ManifestID) ->
103-
lists:filter(
151+
add_bundle_tags(TX) ->
152+
ManifestID = ar_bundles:id(TX#tx.manifest, unsigned),
153+
BundleTags = ?BUNDLE_TAGS ++ [{<<"bundle-map">>, hb_util:encode(ManifestID)}],
154+
StrippedTags = lists:filter(
104155
fun({TagName, _}) ->
105-
hb_util:to_lower(TagName) =/= <<"bundle-map">>
156+
not lists:member(hb_util:to_lower(TagName), ?BUNDLE_KEYS)
106157
end,
107-
Tags
108-
) ++ [{<<"bundle-map">>, hb_util:encode(ManifestID)}].
158+
TX#tx.tags
159+
),
160+
TX#tx{tags = BundleTags ++ StrippedTags}.
109161

110162
%% @doc Reset the data size of a data item. Assumes that the data is already normalized.
111163
normalize_data_size(Item = #tx{data = Bin}) when is_binary(Bin) ->

0 commit comments

Comments
 (0)