Skip to content

Commit 8efa64a

Browse files
Merge pull request #104 from permaweb/feat/multi-attestations
impl: multisignature messages and hashpath/cache reorg
2 parents 04b0fae + 323e526 commit 8efa64a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1979
-1530
lines changed

erlang_ls.config

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,11 @@ diagnostics:
66
apps_dirs:
77
- "src"
88
- "src/*"
9-
deps_dirs:
10-
- "_build/default/lib/b64fast"
11-
- "_build/default/lib/cowboy"
12-
- "_build/default/lib/gun"
13-
- "_build/default/lib/jiffy"
149
include_dirs:
1510
- "src/include"
1611
include_dirs:
1712
- "src"
1813
- "src/include"
19-
- "_build/default/lib/"
20-
- "_build/default/lib/*/include"
2114
lenses:
2215
enabled:
2316
- ct-run-test

src/ar_bundles.erl

Lines changed: 3 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
-export([new_item/4, sign_item/2, verify_item/1]).
66
-export([encode_tags/1, decode_tags/1]).
77
-export([serialize/1, serialize/2, deserialize/1, deserialize/2]).
8-
-export([item_to_json_struct/1, json_struct_to_item/1]).
98
-export([data_item_signature_data/1]).
109
-export([normalize/1]).
1110
-export([print/1, format/1, format/2]).
@@ -385,7 +384,7 @@ serialize(RawTX, binary) ->
385384
>>;
386385
serialize(TX, json) ->
387386
true = enforce_valid_tx(TX),
388-
jiffy:encode(item_to_json_struct(TX)).
387+
jiffy:encode(hb_message:convert(TX, <<"ans104@1.0">>, #{})).
389388

390389
%% @doc Take an item and ensure that it is of valid form. Useful for ensuring
391390
%% that a message is viable for serialization/deserialization before execution.
@@ -680,11 +679,8 @@ deserialize(Binary, binary) ->
680679
%end;
681680
deserialize(Bin, json) ->
682681
try
683-
normalize(
684-
maybe_unbundle(
685-
json_struct_to_item(element(1, jiffy:decode(Bin)))
686-
)
687-
)
682+
Map = jiffy:decode(Bin, [return_maps]),
683+
hb_message:convert(Map, <<"ans104@1.0">>, #{})
688684
catch
689685
_:_:_Stack ->
690686
{error, invalid_item}
@@ -781,91 +777,6 @@ decode_bundle_header(0, ItemsBin, Header) ->
781777
decode_bundle_header(Count, <<Size:256/integer, ID:32/binary, Rest/binary>>, Header) ->
782778
decode_bundle_header(Count - 1, Rest, [{ID, Size} | Header]).
783779

784-
item_to_json_struct(
785-
#tx{
786-
id = ID,
787-
last_tx = Last,
788-
owner = Owner,
789-
tags = Tags,
790-
target = Target,
791-
data = Data,
792-
signature = Sig
793-
}
794-
) ->
795-
% Set "From" if From-Process is Tag or set with "Owner" address
796-
?event({invoked_item_to_json_struct, {tags, Tags}, {owner, Owner}, {data, Data}}),
797-
From =
798-
case lists:filter(fun({Name, _}) -> Name =:= <<"From-Process">> end, Tags) of
799-
[{_, FromProcess}] -> FromProcess;
800-
[] -> hb_util:encode(ar_wallet:to_address(Owner))
801-
end,
802-
Fields = [
803-
{<<"Id">>, hb_util:encode(ID)},
804-
% NOTE: In Arweave TXs, these are called "last_tx"
805-
{<<"Anchor">>, hb_util:encode(Last)},
806-
% NOTE: When sent to ao "Owner" is the wallet address
807-
{<<"Owner">>, hb_util:encode(ar_wallet:to_address(Owner))},
808-
{<<"From">>, From},
809-
{<<"Tags">>,
810-
lists:map(
811-
fun({Name, Value}) ->
812-
{
813-
[
814-
{name, maybe_list_to_binary(Name)},
815-
{value, maybe_list_to_binary(Value)}
816-
]
817-
}
818-
end,
819-
Tags
820-
)},
821-
{<<"Target">>, hb_util:encode(Target)},
822-
{<<"Data">>, Data},
823-
{<<"Signature">>, hb_util:encode(Sig)}
824-
],
825-
{Fields}.
826-
827-
maybe_list_to_binary(List) when is_list(List) ->
828-
list_to_binary(List);
829-
maybe_list_to_binary(Bin) ->
830-
Bin.
831-
832-
json_struct_to_item(Map) when is_map(Map) ->
833-
deserialize(jiffy:encode(Map), json);
834-
json_struct_to_item({TXStruct}) ->
835-
json_struct_to_item(TXStruct);
836-
json_struct_to_item(RawTXStruct) ->
837-
TXStruct = [{string:lowercase(FieldName), Value} || {FieldName, Value} <- RawTXStruct],
838-
Tags =
839-
case hb_util:find_value(<<"tags">>, TXStruct) of
840-
undefined ->
841-
[];
842-
Xs ->
843-
Xs
844-
end,
845-
TXID = hb_util:decode(hb_util:find_value(<<"id">>, TXStruct, hb_util:encode(?DEFAULT_ID))),
846-
#tx{
847-
format = ans104,
848-
id = TXID,
849-
last_tx = hb_util:decode(hb_util:find_value(<<"anchor">>, TXStruct, <<>>)),
850-
owner = hb_util:decode(
851-
hb_util:find_value(<<"owner">>, TXStruct, hb_util:encode(?DEFAULT_OWNER))
852-
),
853-
tags =
854-
lists:map(
855-
fun({KeyVals}) ->
856-
{_, Name} = lists:keyfind(<<"name">>, 1, KeyVals),
857-
{_, Value} = lists:keyfind(<<"value">>, 1, KeyVals),
858-
{Name, Value}
859-
end,
860-
Tags
861-
),
862-
target = hb_util:decode(hb_util:find_value(<<"target">>, TXStruct, <<>>)),
863-
data = hb_util:find_value(<<"data">>, TXStruct, <<>>),
864-
signature = hb_util:decode(
865-
hb_util:find_value(<<"signature">>, TXStruct, hb_util:encode(?DEFAULT_SIG))
866-
)
867-
}.
868-
869780
%% @doc Decode the signature from a binary format. Only RSA 4096 is currently supported.
870781
%% Note: the signature type '1' corresponds to RSA 4096 - but it is is written in
871782
%% little-endian format which is why we match on `<<1, 0>>'.

src/ar_http.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ handle_info({gun_error, PID, Reason},
195195
ok
196196
end,
197197
gun:shutdown(PID),
198-
?event(debug, {connection_error, {reason, Reason}}),
198+
?event({connection_error, {reason, Reason}}),
199199
{noreply, State#state{ status_by_pid = StatusByPID2, pid_by_peer = PIDByPeer2 }}
200200
end;
201201

@@ -309,7 +309,7 @@ parse_peer(Peer, Opts) ->
309309
[Host, Port] ->
310310
{binary_to_list(Host), parse_port(Port)};
311311
[Host] ->
312-
{binary_to_list(Host), hb_opts:get(http_port, 443, Opts)}
312+
{binary_to_list(Host), hb_opts:get(port, 443, Opts)}
313313
end.
314314

315315
parse_port(Port) ->
Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
%%% @doc Codec for managing transformations from `ar_bundles`-style Arweave TX
22
%%% records to and from TABMs.
3-
-module(hb_codec_tx).
4-
-export([to/1, from/1]).
3+
-module(dev_codec_ans104).
4+
-export([to/1, from/1, attest/3, verify/3]).
55
-include("include/hb.hrl").
66

77
%% The size at which a value should be made into a body item, instead of a
@@ -11,7 +11,6 @@
1111
-define(TX_KEYS,
1212
[
1313
<<"id">>,
14-
<<"unsigned_id">>,
1514
<<"last_tx">>,
1615
<<"owner">>,
1716
<<"target">>,
@@ -26,6 +25,48 @@
2625
]
2726
).
2827

28+
%% @doc Sign a message using the `priv_wallet` key in the options.
29+
attest(Msg, _Req, Opts) ->
30+
Signed = ar_bundles:sign_item(
31+
to(Msg),
32+
Wallet = hb_opts:get(priv_wallet, no_viable_wallet, Opts)
33+
),
34+
ID = Signed#tx.id,
35+
Owner = Signed#tx.owner,
36+
Sig = Signed#tx.signature,
37+
Address = hb_util:human_id(ar_wallet:to_address(Wallet)),
38+
% Gather the prior attestations.
39+
PriorAttestations = maps:get(<<"attestations">>, Msg, #{}),
40+
Attestation =
41+
#{
42+
<<"attestation-device">> => <<"ans104@1.0">>,
43+
<<"id">> => ID,
44+
<<"owner">> => Owner,
45+
<<"signature">> => Sig
46+
},
47+
AttestationWithHP =
48+
case Msg of
49+
#{ <<"hashpath">> := Hashpath } ->
50+
Attestation#{ <<"hashpath">> => Hashpath };
51+
_ -> Attestation
52+
end,
53+
MsgWithoutHP = maps:without([<<"hashpath">>], Msg),
54+
{ok,
55+
MsgWithoutHP#{
56+
<<"attestations">> =>
57+
PriorAttestations#{
58+
Address => AttestationWithHP
59+
}
60+
}
61+
}.
62+
63+
%% @doc Verify an ANS-104 attestation.
64+
verify(Msg, _Req, _Opts) ->
65+
MsgWithoutAttestations = maps:without([<<"attestations">>], Msg),
66+
TX = to(MsgWithoutAttestations),
67+
Res = ar_bundles:verify_item(TX),
68+
{ok, Res}.
69+
2970
%% @doc Convert a #tx record into a message map recursively.
3071
from(Binary) when is_binary(Binary) -> Binary;
3172
from(TX) when is_record(TX, tx) ->
Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
%%% A codec for turning TABMs into/from flat Erlang maps that have (potentially
2-
%%% multi-layer) paths as their keys, and a normal TABM binary as their value.
3-
-module(hb_codec_flat).
4-
-export([from/1, to/1]).
1+
%%% @doc A codec for turning TABMs into/from flat Erlang maps that have
2+
%%% (potentially multi-layer) paths as their keys, and a normal TABM binary as
3+
%%% their value.
4+
-module(dev_codec_flat).
5+
-export([from/1, to/1, attest/3, verify/3]).
56
-include_lib("eunit/include/eunit.hrl").
67
-include("include/hb.hrl").
78

9+
%%% Route signature functions to the `dev_codec_httpsig' module
10+
attest(Msg, Req, Opts) -> dev_codec_httpsig:attest(Msg, Req, Opts).
11+
verify(Msg, Req, Opts) -> dev_codec_httpsig:verify(Msg, Req, Opts).
12+
813
%% @doc Convert a flat map to a TABM.
914
from(Bin) when is_binary(Bin) -> Bin;
1015
from(Map) when is_map(Map) ->
@@ -54,14 +59,14 @@ to(Map) when is_map(Map) ->
5459
simple_conversion_test() ->
5560
Flat = #{[<<"a">>] => <<"value">>},
5661
Nested = #{<<"a">> => <<"value">>},
57-
?assert(hb_message:match(Nested, hb_codec_flat:from(Flat))),
58-
?assert(hb_message:match(Flat, hb_codec_flat:to(Nested))).
62+
?assert(hb_message:match(Nested, dev_codec_flat:from(Flat))),
63+
?assert(hb_message:match(Flat, dev_codec_flat:to(Nested))).
5964

6065
nested_conversion_test() ->
6166
Flat = #{<<"a/b">> => <<"value">>},
6267
Nested = #{<<"a">> => #{<<"b">> => <<"value">>}},
63-
Unflattened = hb_codec_flat:from(Flat),
64-
Flattened = hb_codec_flat:to(Nested),
68+
Unflattened = dev_codec_flat:from(Flat),
69+
Flattened = dev_codec_flat:to(Nested),
6570
?assert(hb_message:match(Nested, Unflattened)),
6671
?assert(hb_message:match(Flat, Flattened)).
6772

@@ -78,22 +83,22 @@ multiple_paths_test() ->
7883
},
7984
<<"a">> => <<"3">>
8085
},
81-
?assert(hb_message:match(Nested, hb_codec_flat:from(Flat))),
82-
?assert(hb_message:match(Flat, hb_codec_flat:to(Nested))).
86+
?assert(hb_message:match(Nested, dev_codec_flat:from(Flat))),
87+
?assert(hb_message:match(Flat, dev_codec_flat:to(Nested))).
8388

8489
binary_passthrough_test() ->
8590
Bin = <<"raw binary">>,
86-
?assertEqual(Bin, hb_codec_flat:from(Bin)),
87-
?assertEqual(Bin, hb_codec_flat:to(Bin)).
91+
?assertEqual(Bin, dev_codec_flat:from(Bin)),
92+
?assertEqual(Bin, dev_codec_flat:to(Bin)).
8893

8994
deep_nesting_test() ->
9095
Flat = #{<<"a/b/c/d">> => <<"deep">>},
9196
Nested = #{<<"a">> => #{<<"b">> => #{<<"c">> => #{<<"d">> => <<"deep">>}}}},
92-
Unflattened = hb_codec_flat:from(Flat),
93-
Flattened = hb_codec_flat:to(Nested),
97+
Unflattened = dev_codec_flat:from(Flat),
98+
Flattened = dev_codec_flat:to(Nested),
9499
?assert(hb_message:match(Nested, Unflattened)),
95100
?assert(hb_message:match(Flat, Flattened)).
96101

97102
empty_map_test() ->
98-
?assertEqual(#{}, hb_codec_flat:from(#{})),
99-
?assertEqual(#{}, hb_codec_flat:to(#{})).
103+
?assertEqual(#{}, dev_codec_flat:from(#{})),
104+
?assertEqual(#{}, dev_codec_flat:to(#{})).

0 commit comments

Comments
 (0)