4 Commits

Author SHA1 Message Date
zxq9 a6eaf3e17e WIP 2026-06-12 20:45:55 +09:00
zxq9 d7c8f1ec29 WIP: Reorientation 2026-06-12 11:02:47 +09:00
zxq9 a5da9d08f5 WIP 2026-06-11 23:31:39 +09:00
zxq9 853f6b5a8d WIP 2026-06-10 16:53:57 +09:00
27 changed files with 941 additions and 881 deletions
+1
View File
@@ -0,0 +1 @@
{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}.
+7 -8
View File
@@ -1,15 +1,14 @@
ISC License
Copyright (c) 2018, aeternity developers
Copyright 2026 Craig Everett <craigeverett@qpq.swiss>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
THE SOFTWARE IS PROVIDED AS IS AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
+2 -43
View File
@@ -1,47 +1,6 @@
enoise
zNoise
=====
An Erlang implementation of the [Noise protocol](https://noiseprotocol.org/)
`enoise` provides a generic handshake mechanism, that can be used in a couple
of different ways. There is also a plain `gen_tcp`-wrapper, where you can
"upgrade" a TCP socket to a Noise socket and use it in much the same way as you
would use `gen_tcp`.
Interactive handshake
---------------------
When using `enoise` to do an interactive handshake, `enoise` will only take
care of message composition/decompositiona and encryption/decryption - i.e. the
user has to do the actual sending and receiving.
An example of the interactive handshake can be seen in the `noise_interactive`
test in `test/enoise_tests.erl`.
Generic handshake
-----------------
There is also the option to use an automated handshake procedure. If provided
with a generic _Communication state_ that describe how data is sent and
received, the handshake procedure is done automatically. The result of a
successful handshake is two Cipher states that can be used to encrypt/decrypt a
RX channel and a TX channel respectively.
The provided `gen_tcp`-wrapper is implemented using the generic handshake, see
`src/enoise.erl`.
Build
-----
$ rebar3 compile
Test
----
$ rebar3 eunit
Typecheck
---------
$ rebar3 dialyzer
$ elp --eqwalize-all --rebar
This is a fork of [enoise](https://git.qpq.swiss/QPQ-AG/enoise).
-14
View File
@@ -1,14 +0,0 @@
{erl_opts, [debug_info]}.
{plugins, [rebar3_hex]}.
{profiles, [{test, [{deps, [ {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
, {eqwalizer_support, {git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"}, "eqwalizer_support"}}
]}
]}
]}.
{xref_checks, [undefined_function_calls, undefined_functions,
locals_not_used,
deprecated_function_calls, deprecated_functions]}.
{dialyzer, [{warnings, [unknown]}]}.
-1
View File
@@ -1 +0,0 @@
[].
-15
View File
@@ -1,15 +0,0 @@
{application, enoise,
[{description, "An Erlang implementation of the Noise protocol"},
{vsn, "1.2.0"},
{registered, []},
{applications,
[kernel,
stdlib,
crypto
]},
{env,[]},
{modules, []},
{maintainers, ["Hans Svensson"]},
{licenses, ["ISC"]},
{links, [{"Github", "https://github.com/aeternity/enoise"}]}
]}.
-328
View File
@@ -1,328 +0,0 @@
%%% ------------------------------------------------------------------
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc Module is an interface to the Noise protocol
%%% [https://noiseprotocol.org]
%%%
%%% The module implements Noise handshake in `handshake/3'.
%%%
%%% For convenience there is also an API to use Noise over TCP (i.e. `gen_tcp')
%%% and after "upgrading" a `gen_tcp'-socket into a `enoise'-socket it has a
%%% similar API as `gen_tcp'.
%%%
%%% @end ------------------------------------------------------------------
-module(enoise).
%% Main function with generic Noise handshake
-export([handshake/2, handshake/3, step_handshake/2]).
%% API exports - Mainly mimicing gen_tcp
-export([ accept/2
, close/1
, connect/2
, controlling_process/2
, send/2
, set_active/2 ]).
-record(enoise, { pid }).
-type noise_key() :: binary().
-type noise_keypair() :: enoise_keypair:keypair().
-type noise_options() :: [noise_option()].
%% A list of Noise options is a proplist, it *must* contain a value `noise'
%% that describes which Noise configuration to use. It is possible to give a
%% `prologue' to the protocol. And for the protocol to work, the correct
%% configuration of pre-defined keys (`s', `e', `rs', `re') should also be
%% provided.
-type noise_option() :: {noise, noise_protocol_option()} %% Required
| {e, noise_keypair()} %% Mandatary depending on `noise'
| {s, noise_keypair()}
| {re, noise_key()}
| {rs, noise_key()}
| {prologue, binary()} %% Optional
| {timeout, integer() | infinity}. %% Optional
-type noise_protocol_option() :: enoise_protocol:protocol() | string() |
binary().
%% Either an instantiated Noise protocol configuration or the name of a Noise
%% configuration (either as a string or a binary string).
-type com_state_state() :: term().
%% The state part of a communiction state
-type recv_msg_fun() :: fun((com_state_state(), integer() | infinity) ->
{ok, binary(), com_state_state()} | {error, term()}).
%% Function that receive a message
-type send_msg_fun() :: fun((com_state_state(), binary()) -> ok).
%% Function that sends a message
-type noise_com_state() :: #{ recv_msg := recv_msg_fun(),
send_msg := send_msg_fun(),
state := term() }.
%% Noise communication state - used to parameterize a handshake. Consists of a
%% send function, one receive function, and an internal state.
-type noise_split_state() :: enoise_hs_state:noise_split_state().
%% Return value from the final `split' operation. Provides a CipherState for
%% receiving and a CipherState transmission. Also includes the final handshake
%% hash for channel binding.
-opaque noise_socket() :: #enoise{}.
%% An abstract Noise socket - holds a reference to a socket that has completed
%% a Noise handshake.
-export_type([noise_socket/0]).
%%====================================================================
%% API functions
%%====================================================================
%% @doc Start an interactive handshake
%% @end
-spec handshake(Options :: noise_options(),
Role :: enoise_hs_state:noise_role()) ->
{ok, enoise_hs_state:state()} | {error, term()}.
handshake(Options, Role) ->
create_hstate(Options, Role).
%% @doc Do a step (either `{send, Payload}', `{rcvd, EncryptedData}',
%% or `done')
%% @end
-spec step_handshake(HState :: enoise_hs_state:state(),
Data :: {rcvd, binary()} | {send, binary()}) ->
{ok, send, binary(), enoise_hs_state:state()}
| {ok, rcvd, binary(), enoise_hs_state:state()}
| {ok, done, noise_split_state()}
| {error, term()}.
step_handshake(HState, Data) ->
do_step_handshake(HState, Data).
%% @doc Perform a Noise handshake
%% @end
-spec handshake(Options :: noise_options(),
Role :: enoise_hs_state:noise_role(),
ComState :: noise_com_state()) ->
{ok, noise_split_state(), noise_com_state()} | {error, term()}.
handshake(Options, Role, ComState) ->
case create_hstate(Options, Role) of
{ok, HState} ->
Timeout = proplists:get_value(timeout, Options, infinity),
do_handshake(HState, ComState, Timeout);
Err = {error, _} ->
Err
end.
%% @doc Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
%% that is, performs the client-side noise handshake.
%%
%% Note: The TCP socket has to be in mode `{active, true}' or `{active, once}',
%% passive receive is not supported.
%%
%% {@link noise_options()} is a proplist.
%% @end
-spec connect(TcpSock :: gen_tcp:socket(),
Options :: noise_options()) ->
{ok, noise_socket(), enoise_hs_state:state()} | {error, term()}.
connect(TcpSock, Options) ->
tcp_handshake(TcpSock, initiator, Options).
%% @doc Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
%% that is, performs the server-side noise handshake.
%%
%% Note: The TCP socket has to be in mode `{active, true}' or `{active, once}',
%% passive receive is not supported.
%%
%% {@link noise_options()} is a proplist.
%% @end
-spec accept(TcpSock :: gen_tcp:socket(),
Options :: noise_options()) ->
{ok, noise_socket(), enoise_hs_state:state()} | {error, term()}.
accept(TcpSock, Options) ->
tcp_handshake(TcpSock, responder, Options).
%% @doc Writes `Data' to `Socket'
%% @end
-spec send(Socket :: noise_socket(), Data :: binary()) -> ok | {error, term()}.
send(#enoise{ pid = Pid }, Data) ->
enoise_connection:send(Pid, Data).
%% @doc Closes a Noise connection.
%% @end
-spec close(NoiseSock :: noise_socket()) -> ok | {error, term()}.
close(#enoise{ pid = Pid }) ->
enoise_connection:close(Pid).
%% @doc Assigns a new controlling process to the Noise socket. A controlling
%% process is the owner of an Noise socket, and receives all messages from the
%% socket.
%% @end
-spec controlling_process(Socket :: noise_socket(), Pid :: pid()) ->
ok | {error, term()}.
controlling_process(#enoise{ pid = Pid }, NewPid) ->
enoise_connection:controlling_process(Pid, NewPid).
%% @doc Set the active option `true | once'. Note that `N' and `false' are
%% not valid options for a Noise socket.
%% @end
-spec set_active(Socket :: noise_socket(), Mode :: true | once) ->
ok | {error, term()}.
set_active(#enoise{ pid = Pid }, ActiveMode) ->
enoise_connection:set_active(Pid, ActiveMode).
%%====================================================================
%% Internal functions
%%====================================================================
do_handshake(HState, ComState, Timeout) ->
case enoise_hs_state:next_message(HState) of
in ->
case hs_recv_msg(ComState, Timeout) of
{ok, Data, ComState1} ->
case enoise_hs_state:read_message(HState, Data) of
{ok, HState1, _Msg} ->
do_handshake(HState1, ComState1, Timeout);
Err = {error, _} ->
Err
end;
Err = {error, _} ->
Err
end;
out ->
{ok, HState1, Msg} = enoise_hs_state:write_message(HState, <<>>),
case hs_send_msg(ComState, Msg) of
{ok, ComState1} ->
do_handshake(HState1, ComState1, Timeout);
Err = {error, _} ->
Err
end;
done ->
{ok, Res} = enoise_hs_state:finalize(HState),
{ok, Res, ComState}
end.
hs_recv_msg(CS = #{ recv_msg := Recv, state := S }, Timeout) ->
case Recv(S, Timeout) of
{ok, Data, S1} -> {ok, Data, CS#{ state := S1 }};
Err = {error, _} -> Err
end.
hs_send_msg(CS = #{ send_msg := Send, state := S }, Data) ->
case Send(S, Data) of
{ok, S1} -> {ok, CS#{ state := S1 }};
Err = {error, _} -> Err
end.
do_step_handshake(HState, Data) ->
case {enoise_hs_state:next_message(HState), Data} of
{in, {rcvd, Encrypted}} ->
case enoise_hs_state:read_message(HState, Encrypted) of
{ok, HState1, Msg} ->
{ok, rcvd, Msg, HState1};
Err = {error, _} ->
Err
end;
{out, {send, Payload}} ->
{ok, HState1, Msg} = enoise_hs_state:write_message(HState, Payload),
{ok, send, Msg, HState1};
{done, done} ->
{ok, Res} = enoise_hs_state:finalize(HState),
{ok, done, Res};
{Next, _} ->
{error, {invalid_step, expected, Next, got, Data}}
end.
%% -- gen_tcp specific functions ---------------------------------------------
tcp_handshake(TcpSock, Role, Options) ->
case check_gen_tcp(TcpSock) of
ok ->
case inet:getopts(TcpSock, [active]) of
{ok, [{active, Active}]} ->
do_tcp_handshake(Options, Role, TcpSock, Active);
Err = {error, _} ->
Err
end;
Err = {error, _} ->
Err
end.
do_tcp_handshake(Options, Role, TcpSock, Active) ->
ComState = #{ recv_msg => fun gen_tcp_rcv_msg/2,
send_msg => fun gen_tcp_snd_msg/2,
state => {TcpSock, Active, <<>>} },
case handshake(Options, Role, ComState) of
{ok, #{ rx := Rx, tx := Tx, final_state := FState }, #{ state := {_, _, Buf} }} ->
case enoise_connection:start_link(TcpSock, Rx, Tx, self(), {Active, Buf}) of
{ok, Pid} -> {ok, #enoise{ pid = Pid }, FState};
Err = {error, _} -> Err
end;
Err = {error, _} ->
Err
end.
create_hstate(Options, Role) ->
Prologue = proplists:get_value(prologue, Options, <<>>),
NoiseProtocol0 = proplists:get_value(noise, Options),
NoiseProtocol =
case NoiseProtocol0 of
X when is_binary(X); is_list(X) ->
enoise_protocol:from_name(X);
_ -> NoiseProtocol0
end,
DH = enoise_protocol:dh(NoiseProtocol),
S = proplists:get_value(s, Options, undefined),
E = proplists:get_value(e, Options, undefined),
RS = remote_keypair(DH, proplists:get_value(rs, Options, undefined)),
RE = remote_keypair(DH, proplists:get_value(re, Options, undefined)),
enoise_hs_state:init(NoiseProtocol, Role,
Prologue, {S, E, RS, RE}).
check_gen_tcp(TcpSock) ->
case inet:getopts(TcpSock, [mode, packet, active, header, packet_size]) of
{ok, TcpOpts} ->
Packet = proplists:get_value(packet, TcpOpts, 0),
Active = proplists:get_value(active, TcpOpts, 0),
Header = proplists:get_value(header, TcpOpts, 0),
PSize = proplists:get_value(packet_size, TcpOpts, undefined),
Mode = proplists:get_value(mode, TcpOpts, binary),
case (Packet == 0 orelse Packet == raw)
andalso (Active == true orelse Active == once)
andalso Header == 0 andalso PSize == 0 andalso Mode == binary of
true ->
gen_tcp:controlling_process(TcpSock, self());
false ->
{error, {invalid_tcp_options, TcpOpts}}
end;
Err = {error, _} ->
Err
end.
gen_tcp_snd_msg(S = {TcpSock, _, _}, Msg) ->
Len = byte_size(Msg),
case gen_tcp:send(TcpSock, <<Len:16, Msg/binary>>) of
ok -> {ok, S};
Err = {error, _} -> Err
end.
gen_tcp_rcv_msg({TcpSock, Active, Buf}, Timeout) ->
receive {tcp, TcpSock, Data} ->
%% Immediately re-set {active, once}
[ inet:setopts(TcpSock, [{active, once}]) || Active == once ],
case <<Buf/binary, Data/binary>> of
Buf1 = <<Len:16, Rest/binary>> when byte_size(Rest) < Len ->
gen_tcp_rcv_msg({TcpSock, true, Buf1}, Timeout);
<<Len:16, Rest/binary>> ->
<<Data1:Len/binary, Buf1/binary>> = Rest,
{ok, Data1, {TcpSock, true, Buf1}}
end
after Timeout ->
{error, timeout}
end.
remote_keypair(_DH, undefined) -> undefined;
remote_keypair(DH, RemotePub) when is_binary(RemotePub) -> enoise_keypair:new(DH, RemotePub).
-201
View File
@@ -1,201 +0,0 @@
%%% ------------------------------------------------------------------
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc Module implementing a gen_server for holding a handshaked
%%% Noise connection over gen_tcp.
%%%
%%% Some care is needed since the underlying transmission is broken up
%%% into Noise packets, so we need some buffering.
%%%
%%% @end
%%% ------------------------------------------------------------------
-module(enoise_connection).
-export([ controlling_process/2
, close/1
, send/2
, set_active/2
, start_link/5
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(enoise, { pid }).
-record(state, {rx, tx, owner, owner_ref, tcp_sock, active, msgbuf = [], rawbuf = <<>>}).
%% -- API --------------------------------------------------------------------
start_link(TcpSock, Rx, Tx, Owner, {Active0, Buf}) ->
Active = case Active0 of
true -> true;
once -> {once, false}
end,
State = #state{ rx = Rx, tx = Tx, owner = Owner,
tcp_sock = TcpSock, active = Active },
case gen_server:start_link(?MODULE, [State], []) of
{ok, Pid} ->
case gen_tcp:controlling_process(TcpSock, Pid) of
ok ->
%% Changing controlling process require a bit of
%% fiddling with already received and delivered content...
[ Pid ! {tcp, TcpSock, Buf} || Buf /= <<>> ],
flush_tcp(Pid, TcpSock),
{ok, Pid};
Err = {error, _} ->
close(Pid),
Err
end;
Err = {error, _} ->
Err
end.
-spec send(Noise :: pid(), Data :: binary()) -> ok | {error, term()}.
send(Noise, Data) ->
gen_server:call(Noise, {send, Data}).
-spec set_active(Noise :: pid(), Active :: true | once) -> ok | {error, term()}.
set_active(Noise, Active) ->
gen_server:call(Noise, {active, self(), Active}).
-spec close(Noise :: pid()) -> ok | {error, term()}.
close(Noise) ->
gen_server:call(Noise, close).
-spec controlling_process(Noise :: pid(), NewPid :: pid()) -> ok | {error, term()}.
controlling_process(Noise, NewPid) ->
gen_server:call(Noise, {controlling_process, self(), NewPid}, 100).
%% -- gen_server callbacks ---------------------------------------------------
init([#state{owner = Owner} = State]) ->
OwnerRef = erlang:monitor(process, Owner),
{ok, State#state{owner_ref = OwnerRef}}.
handle_call(close, _From, S) ->
{stop, normal, ok, S};
handle_call(_Call, _From, S = #state{ tcp_sock = closed }) ->
{reply, {error, closed}, S};
handle_call({send, Data}, _From, S) ->
{Res, S1} = handle_send(S, Data),
{reply, Res, S1};
handle_call({controlling_process, OldPid, NewPid}, _From, S) ->
{Res, S1} = handle_control_change(S, OldPid, NewPid),
{reply, Res, S1};
handle_call({active, Pid, NewActive}, _From, S) ->
{Res, S1} = handle_active(S, Pid, NewActive),
{reply, Res, S1}.
handle_cast(_Msg, S) ->
{noreply, S}.
handle_info({tcp, TS, Data}, S = #state{ tcp_sock = TS, owner = O }) ->
try
{S1, Msgs} = handle_data(S, Data),
S2 = handle_msgs(S1#state{ msgbuf = S1#state.msgbuf ++ Msgs }),
set_active(S2),
{noreply, S2}
catch error:{enoise_error, _} ->
%% We are not likely to recover, but leave the decision to upstream
O ! {enoise_error, TS, decrypt_error},
{noreply, S}
end;
handle_info({tcp_closed, TS}, S = #state{ tcp_sock = TS, owner = O }) ->
O ! {tcp_closed, TS},
{noreply, S#state{ tcp_sock = closed }};
handle_info({'DOWN', OwnerRef, process, _, normal},
S = #state { tcp_sock = TS, owner_ref = OwnerRef }) ->
close_tcp(TS),
{stop, normal, S#state{ tcp_sock = closed, owner_ref = undefined }};
handle_info({'DOWN', _, _, _, _}, S) ->
%% Ignore non-normal monitor messages - we are linked.
{noreply, S};
handle_info(_Msg, S) ->
{noreply, S}.
terminate(_Reason, #state{ tcp_sock = TcpSock, owner_ref = ORef }) ->
[ gen_tcp:close(TcpSock) || TcpSock /= closed ],
[ erlang:demonitor(ORef, [flush]) || ORef /= undefined ],
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% -- Local functions --------------------------------------------------------
handle_control_change(S = #state{ owner = Pid, owner_ref = OldRef }, Pid, NewPid) ->
NewRef = erlang:monitor(process, NewPid),
erlang:demonitor(OldRef, [flush]),
{ok, S#state{ owner = NewPid, owner_ref = NewRef }};
handle_control_change(S, _OldPid, _NewPid) ->
{{error, not_owner}, S}.
handle_active(S = #state{ owner = Pid, tcp_sock = TcpSock }, Pid, Active) ->
case Active of
true ->
inet:setopts(TcpSock, [{active, true}]),
{ok, handle_msgs(S#state{ active = true })};
once ->
S1 = handle_msgs(S#state{ active = {once, false} }),
set_active(S1),
{ok, S1}
end;
handle_active(S, _Pid, _NewActive) ->
{{error, not_owner}, S}.
handle_data(S = #state{ rawbuf = Buf, rx = Rx }, Data) ->
case <<Buf/binary, Data/binary>> of
B = <<Len:16, Rest/binary>> when Len > byte_size(Rest) ->
{S#state{ rawbuf = B }, []}; %% Not a full Noise message - save it
<<Len:16, Rest/binary>> ->
<<Msg:Len/binary, Rest2/binary>> = Rest,
case enoise_cipher_state:decrypt_with_ad(Rx, <<>>, Msg) of
{ok, Rx1, Msg1} ->
{S1, Msgs} = handle_data(S#state{ rawbuf = Rest2, rx = Rx1 }, <<>>),
{S1, [Msg1 | Msgs]};
{error, _} ->
error({enoise_error, decrypt_input_failed})
end;
EmptyOrSingleByte ->
{S#state{ rawbuf = EmptyOrSingleByte }, []}
end.
handle_msgs(S = #state{ msgbuf = [] }) ->
S;
handle_msgs(S = #state{ msgbuf = Msgs, active = true, owner = Owner }) ->
[ Owner ! {noise, #enoise{ pid = self() }, Msg} || Msg <- Msgs ],
S#state{ msgbuf = [] };
handle_msgs(S = #state{ msgbuf = [Msg | Msgs], active = {once, Delivered}, owner = Owner }) ->
case Delivered of
true ->
S;
false ->
Owner ! {noise, #enoise{ pid = self() }, Msg},
S#state{ msgbuf = Msgs, active = {once, true} }
end.
handle_send(S = #state{ tcp_sock = TcpSock, tx = Tx }, Data) ->
{ok, Tx1, Msg} = enoise_cipher_state:encrypt_with_ad(Tx, <<>>, Data),
case gen_tcp:send(TcpSock, <<(byte_size(Msg)):16, Msg/binary>>) of
ok -> {ok, S#state{ tx = Tx1 }};
Err = {error, _} -> {Err, S}
end.
set_active(#state{ msgbuf = [], active = {once, _}, tcp_sock = TcpSock }) ->
inet:setopts(TcpSock, [{active, once}]);
set_active(_) ->
ok.
flush_tcp(Pid, TcpSock) ->
receive {tcp, TcpSock, Data} ->
Pid ! {tcp, TcpSock, Data},
flush_tcp(Pid, TcpSock)
after 1 -> ok
end.
close_tcp(closed) ->
ok;
close_tcp(Sock) ->
gen_tcp:close(Sock).
+377
View File
@@ -0,0 +1,377 @@
%%% @copyright 2026, QPQ AG
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc
%%% Interface to the Noise protocol: https://noiseprotocol.org
%%%
%%% This is a fork of the `enoise' project: https://git.qpq.swiss/QPQ-AG/enoise
%%%
%%% For convenience there is also an API to use Noise over TCP (i.e. `gen_tcp')
%%% and after "upgrading" a `gen_tcp'-socket into a `znoise'-socket it has a
%%% similar API as `gen_tcp'.
%%% @end
-module(znoise).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-author("Hans Svensson <hanssv@gmail.com>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
%% Main function with generic Noise handshake
-export([handshake/2, handshake/3, step_handshake/2]).
%% API exports - Mainly mimicing gen_tcp
-export([accept/2,
close/1,
connect/2,
controlling_process/2,
send/2,
set_active/2]).
-record(znoise,
{pid = none | pid()}).
-type key() :: binary().
-type keypair() :: znoise_keypair:keypair().
-type options() :: [option()].
%% A list of Noise options is a proplist, it *must* contain a value `noise'
%% that describes which Noise configuration to use. It is possible to give
%% a `prologue' to the protocol. And for the protocol to work, the correct
%% configuration of pre-defined keys (`s', `e', `rs', `re') should also be
%% provided.
-type option() :: {noise, protocol_option()} %% Required
| {e, keypair()} %% Mandatary depending on `noise'
| {s, keypair()}
| {re, key()}
| {rs, key()}
| {prologue, binary()} %% Optional
| {timeout, integer() | infinity}. %% Optional
-type protocol_option() :: znoise_protocol:protocol()
| string()
| binary().
%% Either an instantiated Noise protocol configuration or the name of a Noise
%% configuration (either as a string or a binary string).
-type com_state_state() :: term().
%% The state part of a communiction state
-type timeout() :: pos_integer() | infinity.
-type recv_return() :: {ok, binary(), com_state_state()}
| {error, term()}).
-type recv_msg_fun() :: fun((com_state_state(), timeout()) -> recv_return()).
-type send_msg_fun() :: fun((com_state_state(), binary()) -> ok).
-type com_state() :: #{recv_msg := recv_msg_fun(),
send_msg := send_msg_fun(),
state := term()}.
%% Noise communication state - used to parameterize a handshake. Consists of a
%% send function, one receive function, and an internal state.
-type split_state() :: znoise_hs_state:noise_split_state().
%% Return value from the final `split' operation. Provides a CipherState for
%% receiving and a CipherState transmission. Also includes the final handshake
%% hash for channel binding.
-opaque socket() :: #znoise{}.
%% An abstract Noise socket - holds a reference to a socket that has completed
%% a Noise handshake.
-export_type([socket/0]).
%%% API functions
-spec handshake(Options, Role) -> Outcome
when Options :: options(),
Role :: znoise_hs_state:noise_role(),
Outcome :: {ok, znoise_hs_state:state()}
| {error, term()}.
%% @doc
%% Start an interactive handshake
handshake(Options, Role) ->
create_hstate(Options, Role).
-spec handshake(Options, Role, ComState) -> Outcome
when Options :: options(),
Role :: znoise_hs_state:noise_role(),
ComState :: com_state(),
Outcome :: {ok, split_state(), com_state()}
| {error, term()}.
%% @doc
%% Perform a Noise handshake
handshake(Options, Role, ComState) ->
case create_hstate(Options, Role) of
{ok, HState} ->
Timeout = proplists:get_value(timeout, Options, infinity),
do_handshake(HState, ComState, Timeout);
Err = {error, _} ->
Err
end.
-spec step_handshake(HState, Data) -> Next
when HState :: znoise_hs_state:state(),
Data :: {rcvd, binary()}
| {send, binary()},
Next :: {ok, send, binary(), znoise_hs_state:state()}
| {ok, rcvd, binary(), znoise_hs_state:state()}
| {ok, done, split_state()}
| {error, term()}.
%% @doc
%% Do a step (one of `{send, Payload}', `{rcvd, EncryptedData}', or `done')
step_handshake(HState, Data) ->
do_step_handshake(HState, Data).
-spec connect(TcpSock, Options) -> Outcome
when TcpSock :: gen_tcp:socket(),
Options :: options(),
Outcome :: {ok, socket(), znoise_hs_state:state()}
| {error, term()}.
%% @doc
%% Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
%% that is, performs the client-side noise handshake.
%%
%% Note: The TCP socket has to be in mode `{active, true}' or `{active, once}',
%% passive receive is not supported.
%%
%% {@link options()} is a proplist.
connect(TcpSock, Options) ->
tcp_handshake(TcpSock, initiator, Options).
-spec accept(TcpSock, Options) -> Outcome
when TcpSock :: gen_tcp:socket(),
Options :: options(),
Outcome :: {ok, socket(), znoise_hs_state:state()}
| {error, term()}.
%% @doc
%% Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
%% that is, performs the server-side noise handshake.
%%
%% Note: The TCP socket has to be in mode `{active, true}' or `{active, once}',
%% passive receive is not supported.
%%
%% {@link options()} is a proplist.
accept(TcpSock, Options) ->
tcp_handshake(TcpSock, responder, Options).
-spec send(Socket, Data) -> Outcome
when Socket :: socket(),
Data :: binary(),
Outcome :: ok | {error, term()}.
%% @doc
%% Writes `Data' to `Socket'
send(#znoise{ pid = Pid }, Data) ->
znoise_tcp:send(Pid, Data).
-spec close(NoiseSock) -> Outcome
when NoiseSock :: socket()
Outcome :: ok | {error, term()}.
%% @doc
%% Closes a Noise connection.
close(#znoise{ pid = Pid }) ->
znoise_tcp:close(Pid).
-spec controlling_process(Socket, PID) -> Outcome
when Socket :: socket(),
PID :: pid(),
Outcome :: ok | {error, term()}.
%% @doc
%% Assigns a new controlling process to the Noise socket. A controlling
%% process is the owner of an Noise socket, and receives all messages from the
%% socket.
controlling_process(#znoise{pid = PID}, NewPID) ->
znoise_tcp:controlling_process(PID, NewPID).
-spec set_active(Socket, Mode) -> Outcome
when Socket :: socket(),
Mode :: true | once,
Outcome :: ok | {error, term()}.
%% @doc
%% Set the active option `true | once'. Note that `N' and `false' are
%% not valid options for a Noise socket.
set_active(#znoise{ pid = Pid }, ActiveMode) ->
znoise_tcp:set_active(Pid, ActiveMode).
%%% Internal functions
do_handshake(HState, ComState, Timeout) ->
case znoise_hs_state:next_message(HState) of
in ->
case hs_recv_msg(ComState, Timeout) of
{ok, Data, ComState1} ->
case znoise_hs_state:read_message(HState, Data) of
{ok, HState1, _Msg} ->
do_handshake(HState1, ComState1, Timeout);
Err = {error, _} ->
Err
end;
Err = {error, _} ->
Err
end;
out ->
{ok, HState1, Msg} = znoise_hs_state:write_message(HState, <<>>),
case hs_send_msg(ComState, Msg) of
{ok, ComState1} ->
do_handshake(HState1, ComState1, Timeout);
Err = {error, _} ->
Err
end;
done ->
{ok, Res} = znoise_hs_state:finalize(HState),
{ok, Res, ComState}
end.
hs_recv_msg(CS = #{ recv_msg := Recv, state := S }, Timeout) ->
case Recv(S, Timeout) of
{ok, Data, S1} -> {ok, Data, CS#{ state := S1 }};
Err = {error, _} -> Err
end.
hs_send_msg(CS = #{ send_msg := Send, state := S }, Data) ->
case Send(S, Data) of
{ok, S1} -> {ok, CS#{ state := S1 }};
Err = {error, _} -> Err
end.
do_step_handshake(HState, Data) ->
case {znoise_hs_state:next_message(HState), Data} of
{in, {rcvd, Encrypted}} ->
case znoise_hs_state:read_message(HState, Encrypted) of
{ok, HState1, Msg} ->
{ok, rcvd, Msg, HState1};
Err = {error, _} ->
Err
end;
{out, {send, Payload}} ->
{ok, HState1, Msg} = znoise_hs_state:write_message(HState, Payload),
{ok, send, Msg, HState1};
{done, done} ->
{ok, Res} = znoise_hs_state:finalize(HState),
{ok, done, Res};
{Next, _} ->
{error, {invalid_step, expected, Next, got, Data}}
end.
%% -- gen_tcp specific functions ---------------------------------------------
tcp_handshake(TcpSock, Role, Options) ->
case check_gen_tcp(TcpSock) of
ok ->
case inet:getopts(TcpSock, [active]) of
{ok, [{active, Active}]} ->
do_tcp_handshake(Options, Role, TcpSock, Active);
Err = {error, _} ->
Err
end;
Err = {error, _} ->
Err
end.
do_tcp_handshake(Options, Role, TcpSock, Active) ->
ComState = #{recv_msg => fun gen_tcp_rcv_msg/2,
send_msg => fun gen_tcp_snd_msg/2,
state => {TcpSock, Active, <<>>}},
case handshake(Options, Role, ComState) of
{ok, #{rx := Rx, tx := Tx, final_state := FState}, #{state := {_, _, Buf}}} ->
case znoise_tcp:start_link(TcpSock, Rx, Tx, self(), {Active, Buf}) of
{ok, Pid} -> {ok, #znoise{ pid = Pid }, FState};
Error -> Error
end;
Error ->
Error
end.
create_hstate(Options, Role) ->
Prologue = proplists:get_value(prologue, Options, <<>>),
Noise = proplists:get_value(noise, Options),
Protocol =
case is_binary(Noise) orelse is_list(Noise) of
true -> znoise_protocol:from_name(X);
false -> Noise
end,
DH = znoise_protocol:dh(Protocol),
S = proplists:get_value(s, Options, undefined),
E = proplists:get_value(e, Options, undefined),
RS = remote_keypair(DH, proplists:get_value(rs, Options, undefined)),
RE = remote_keypair(DH, proplists:get_value(re, Options, undefined)),
znoise_hs_state:init(Protocol, Role, Prologue, {S, E, RS, RE}).
check_gen_tcp(TcpSock) ->
case inet:getopts(TcpSock, [mode, packet, active, header, packet_size]) of
{ok, TcpOpts} ->
Packet = proplists:get_value(packet, TcpOpts, 0),
Active = proplists:get_value(active, TcpOpts, 0),
Header = proplists:get_value(header, TcpOpts, 0),
PSize = proplists:get_value(packet_size, TcpOpts, undefined),
Mode = proplists:get_value(mode, TcpOpts, binary),
case
(Packet == 0 orelse Packet == raw)
andalso (Active == true orelse Active == once)
andalso Header == 0
andalso PSize == 0
andalso Mode == binary of
true ->
gen_tcp:controlling_process(TcpSock, self());
false ->
{error, {invalid_tcp_options, TcpOpts}}
end;
Error ->
Error
end.
gen_tcp_snd_msg(S = {TcpSock, _, _}, Msg) ->
Len = byte_size(Msg),
case gen_tcp:send(TcpSock, <<Len:16, Msg/binary>>) of
ok -> {ok, S};
Error -> Error
end.
gen_tcp_rcv_msg({TcpSock, Active, Buf}, Timeout) ->
receive
{tcp, TcpSock, Data} ->
%% Immediately re-set {active, once}
[inet:setopts(TcpSock, [{active, once}]) || Active == once],
case <<Buf/binary, Data/binary>> of
Buf1 = <<Len:16, Rest/binary>> when byte_size(Rest) < Len ->
gen_tcp_rcv_msg({TcpSock, true, Buf1}, Timeout);
<<Len:16, Rest/binary>> ->
<<Data1:Len/binary, Buf1/binary>> = Rest,
{ok, Data1, {TcpSock, true, Buf1}}
end
after Timeout ->
{error, timeout}
end.
remote_keypair(_DH, undefined) ->
undefined;
remote_keypair(DH, RemotePub) when is_binary(RemotePub) ->
znoise_keypair:new(DH, RemotePub).
@@ -1,12 +1,16 @@
%%% ------------------------------------------------------------------
%%% @copyright 2026, QPQ AG
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc Module encapsulating a Noise Cipher state
%%%
%%% @doc
%%% Module encapsulating a Noise Cipher state
%%% @end
%%% ------------------------------------------------------------------
-module(enoise_cipher_state).
-module(znoise_cipher_state).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-author("Hans Svensson <hanssv@gmail.com>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([ cipher/1
, decrypt_with_ad/3
@@ -19,7 +23,7 @@
, set_nonce/2
]).
-include("enoise.hrl").
-include("znoise.hrl").
-type noise_cipher() :: 'ChaChaPoly' | 'AESGCM'.
-type nonce() :: non_neg_integer().
@@ -54,7 +58,7 @@ set_nonce(CState = #noise_cs{}, Nonce) ->
encrypt_with_ad(CState = #noise_cs{ k = empty }, _AD, PlainText) ->
{ok, CState, PlainText};
encrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, PlainText) ->
CipherText = enoise_crypto:encrypt(Cipher, K, N, AD, PlainText),
CipherText = znoise_crypto:encrypt(Cipher, K, N, AD, PlainText),
{ok, CState#noise_cs{ n = N+1 }, CipherText}.
-spec decrypt_with_ad(CState :: state(), AD :: binary(), CipherText :: binary()) ->
@@ -62,7 +66,7 @@ encrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, PlainTe
decrypt_with_ad(CState = #noise_cs{ k = empty }, _AD, CipherText) ->
{ok, CState, CipherText};
decrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, CipherText) ->
case enoise_crypto:decrypt(Cipher, K, N, AD, CipherText) of
case znoise_crypto:decrypt(Cipher, K, N, AD, CipherText) of
PlainText when is_binary(PlainText) ->
{ok, CState#noise_cs{ n = N+1 }, PlainText};
Err = {error, _} ->
@@ -73,7 +77,7 @@ decrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, CipherT
rekey(CState = #noise_cs{ k = empty }) ->
CState;
rekey(CState = #noise_cs{ k = K, cipher = Cipher }) ->
CState#noise_cs{ k = enoise_crypto:rekey(Cipher, K) }.
CState#noise_cs{ k = znoise_crypto:rekey(Cipher, K) }.
-spec cipher(CState :: state()) -> noise_cipher().
cipher(#noise_cs{ cipher = Cipher }) ->
+22 -18
View File
@@ -1,14 +1,18 @@
%%% ------------------------------------------------------------------
%%% @copyright 2026, QPQ AG
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc Module implementing crypto primitives needed by Noise protocol
%%%
%%% @doc
%%% Module implementing crypto primitives needed by Noise protocol
%%% @end
%%% ------------------------------------------------------------------
-module(enoise_crypto).
-module(znoise_crypto).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-author("Hans Svensson <hanssv@gmail.com>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-include("enoise.hrl").
-include("znoise.hrl").
-export([ decrypt/5
, dh/3
@@ -24,14 +28,14 @@
-define(MAC_LEN, 16).
-type keypair() :: enoise_keypair:keypair().
-type keypair() :: znoise_keypair:keypair().
%% @doc Perform a Diffie-Hellman calculation with the secret key from `Key1'
%% and the public key from `Key2' with algorithm `Algo'.
-spec dh(Algo :: enoise_hs_state:noise_dh(),
-spec dh(Algo :: znoise_hs_state:noise_dh(),
Key1:: keypair(), Key2 :: keypair()) -> binary().
dh(Type, Key1, Key2) when Type == dh25519; Type == dh448 ->
dh_(ecdh_type(Type), enoise_keypair:pubkey(Key2), enoise_keypair:seckey(Key1));
dh_(ecdh_type(Type), znoise_keypair:pubkey(Key2), znoise_keypair:seckey(Key1));
dh(Type, _Key1, _Key2) ->
error({unsupported_diffie_hellman, Type}).
@@ -41,7 +45,7 @@ ecdh_type(dh448) -> x448.
dh_(DHType, OtherPub, MyPriv) ->
crypto:compute_key(ecdh, OtherPub, MyPriv, DHType).
-spec hmac(Hash :: enoise_sym_state:noise_hash(),
-spec hmac(Hash :: znoise_sym_state:noise_hash(),
Key :: binary(), Data :: binary()) -> binary().
hmac(Hash, Key, Data) ->
BLen = blocklen(Hash),
@@ -50,7 +54,7 @@ hmac(Hash, Key, Data) ->
Block2 = hmac_format_key(Hash, Key, 16#5C, BLen),
hash(Hash, <<Block2/binary, Hash1/binary>>).
-spec hkdf(Hash :: enoise_sym_state:noise_hash(),
-spec hkdf(Hash :: znoise_sym_state:noise_hash(),
Key :: binary(), Data :: binary()) -> [binary()].
hkdf(Hash, Key, Data) ->
TempKey = hmac(Hash, Key, Data),
@@ -59,7 +63,7 @@ hkdf(Hash, Key, Data) ->
Output3 = hmac(Hash, TempKey, <<Output2/binary, 3:8>>),
[Output1, Output2, Output3].
-spec rekey(Cipher :: enoise_cipher_state:noise_cipher(), Key :: binary()) -> binary().
-spec rekey(Cipher :: znoise_cipher_state:noise_cipher(), Key :: binary()) -> binary().
rekey('ChaChaPoly', K0) ->
KLen = 32,
<<K:KLen/binary, _/binary>> = encrypt('ChaChaPoly', K0, ?MAX_NONCE, <<>>, <<0:(32*8)>>),
@@ -67,13 +71,13 @@ rekey('ChaChaPoly', K0) ->
rekey(Cipher, K) ->
encrypt(Cipher, K, ?MAX_NONCE, <<>>, <<0:(32*8)>>).
-spec encrypt(Cipher :: enoise_cipher_state:noise_cipher(), Key :: binary(),
-spec encrypt(Cipher :: znoise_cipher_state:noise_cipher(), Key :: binary(),
Nonce :: non_neg_integer(), Ad :: binary(), PlainText :: binary()) -> binary().
encrypt(Cipher, K, N, Ad, PlainText) ->
{CText, CTag} = crypto:crypto_one_time_aead(cipher(Cipher), K, nonce(Cipher, N), PlainText, Ad, true),
<<CText/binary, CTag/binary>>.
-spec decrypt(Cipher ::enoise_cipher_state:noise_cipher(), Key :: binary(),
-spec decrypt(Cipher ::znoise_cipher_state:noise_cipher(), Key :: binary(),
Nonce :: non_neg_integer(), AD :: binary(),
CipherText :: binary()) -> binary() | {error, term()}.
decrypt(Cipher, K, N, Ad, CipherText0) ->
@@ -90,7 +94,7 @@ nonce('AESGCM', N) -> <<0:32, N:64/big-unsigned-integer>>.
cipher('ChaChaPoly') -> chacha20_poly1305;
cipher('AESGCM') -> aes_256_gcm.
-spec hash(Hash :: enoise_sym_state:noise_hash(), Data :: binary()) -> binary().
-spec hash(Hash :: znoise_sym_state:noise_hash(), Data :: binary()) -> binary().
hash(blake2s, Data) ->
crypto:hash(blake2s, Data);
hash(blake2b, Data) ->
@@ -113,19 +117,19 @@ pad(Data, MinSize, PadByte) ->
<<Data/binary, PadData/binary>>
end.
-spec hashlen(Hash :: enoise_sym_state:noise_hash()) -> non_neg_integer().
-spec hashlen(Hash :: znoise_sym_state:noise_hash()) -> non_neg_integer().
hashlen(sha256) -> 32;
hashlen(sha512) -> 64;
hashlen(blake2s) -> 32;
hashlen(blake2b) -> 64.
-spec blocklen(Hash :: enoise_sym_state:noise_hash()) -> non_neg_integer().
-spec blocklen(Hash :: znoise_sym_state:noise_hash()) -> non_neg_integer().
blocklen(sha256) -> 64;
blocklen(sha512) -> 128;
blocklen(blake2s) -> 64;
blocklen(blake2b) -> 128.
-spec dhlen(DH :: enoise_hs_state:noise_dh()) -> non_neg_integer().
-spec dhlen(DH :: znoise_hs_state:noise_dh()) -> non_neg_integer().
dhlen(dh25519) -> 32;
dhlen(dh448) -> 56.
@@ -1,75 +1,79 @@
%%% ------------------------------------------------------------------
%%% @copyright 2026, QPQ AG
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc Module encapsulating a Noise handshake state
%%%
%%% @doc
%%% Module encapsulating a Noise handshake state
%%% @end
%%% ------------------------------------------------------------------
-module(enoise_hs_state).
-module(znoise_hs_state).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-author("Hans Svensson <hanssv@gmail.com>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([ finalize/1
, init/4
, next_message/1
, read_message/2
, remote_keys/1
, write_message/2]).
-export([finalize/1,
init/4,
next_message/1,
read_message/2,
remote_keys/1,
write_message/2]).
-include("enoise.hrl").
-include("znoise.hrl").
-type noise_role() :: initiator | responder.
-type noise_dh() :: dh25519 | dh448.
-type noise_token() :: s | e | ee | ss | es | se.
-type keypair() :: enoise_keypair:keypair().
-type noise_split_state() :: #{ rx := enoise_cipher_state:state(),
tx := enoise_cipher_state:state(),
hs_hash := binary(),
final_state => state() }.
-type keypair() :: znoise_keypair:keypair().
-type noise_split_state() :: #{rx := znoise_cipher_state:state(),
tx := znoise_cipher_state:state(),
hs_hash := binary(),
final_state => state() }.
-type optional_key() :: undefined | keypair().
-type initial_keys() :: {optional_key(), optional_key(), optional_key(), optional_key()}.
-record(noise_hs, { ss :: enoise_sym_state:state()
-record(noise_hs, { ss :: znoise_sym_state:state()
, s :: keypair() | undefined
, e :: keypair() | undefined
, rs :: keypair() | undefined
, re :: keypair() | undefined
, role = initiator :: noise_role()
, dh = dh25519 :: noise_dh()
, msgs = [] :: [enoise_protocol:noise_msg()] }).
, msgs = [] :: [znoise_protocol:noise_msg()] }).
-opaque state() :: #noise_hs{}.
-export_type([noise_dh/0, noise_role/0, noise_split_state/0, noise_token/0, state/0]).
-spec init(Protocol :: enoise_protocol:protocol(), Role :: noise_role(),
-spec init(Protocol :: znoise_protocol:protocol(), Role :: noise_role(),
Prologue :: binary(), Keys :: initial_keys()) -> {ok, state()} | {error, term()}.
init(Protocol, Role, Prologue, {S, E, RS, RE}) ->
SS0 = enoise_sym_state:init(Protocol),
SS1 = enoise_sym_state:mix_hash(SS0, Prologue),
SS0 = znoise_sym_state:init(Protocol),
SS1 = znoise_sym_state:mix_hash(SS0, Prologue),
HS = #noise_hs{ ss = SS1
, s = S, e = E, rs = RS, re = RE
, role = Role
, dh = enoise_protocol:dh(Protocol)
, msgs = enoise_protocol:msgs(Role, Protocol) },
PreMsgs = enoise_protocol:pre_msgs(Role, Protocol),
, dh = znoise_protocol:dh(Protocol)
, msgs = znoise_protocol:msgs(Role, Protocol) },
PreMsgs = znoise_protocol:pre_msgs(Role, Protocol),
pre_mix(PreMsgs, HS).
pre_mix([], HS) -> {ok, HS};
pre_mix([{out, [s]} | Msgs], HS = #noise_hs{ s = S }) when S /= undefined ->
pre_mix(Msgs, mix_hash(HS, enoise_keypair:pubkey(S)));
pre_mix(Msgs, mix_hash(HS, znoise_keypair:pubkey(S)));
pre_mix([{out, [e]} | Msgs], HS = #noise_hs{ e = E }) when E /= undefined ->
pre_mix(Msgs, mix_hash(HS, enoise_keypair:pubkey(E)));
pre_mix(Msgs, mix_hash(HS, znoise_keypair:pubkey(E)));
pre_mix([{in, [s]} | Msgs], HS = #noise_hs{ rs = RS }) when RS /= undefined ->
pre_mix(Msgs, mix_hash(HS, enoise_keypair:pubkey(RS)));
pre_mix(Msgs, mix_hash(HS, znoise_keypair:pubkey(RS)));
pre_mix([{in, [e]} | Msgs], HS = #noise_hs{ re = RE }) when RE /= undefined ->
pre_mix(Msgs, mix_hash(HS, enoise_keypair:pubkey(RE)));
pre_mix(Msgs, mix_hash(HS, znoise_keypair:pubkey(RE)));
pre_mix(_Msg, _HS) ->
{error, invalid_noise_setup}.
-spec finalize(HS :: state()) -> {ok, noise_split_state()} | {error, term()}.
finalize(HS = #noise_hs{ msgs = [], ss = SS, role = Role }) ->
{C1, C2} = enoise_sym_state:split(SS),
HSHash = enoise_sym_state:h(SS),
{C1, C2} = znoise_sym_state:split(SS),
HSHash = znoise_sym_state:h(SS),
Final = #{ hs_hash => HSHash, final_state => HS },
case Role of
initiator -> {ok, Final#{ tx => C1, rx => C2 }};
@@ -117,38 +121,38 @@ read_message(HS, [Token | Tokens], Data0) ->
write_token(HS = #noise_hs{ e = undefined }, e) ->
E = new_key_pair(HS),
PubE = enoise_keypair:pubkey(E),
PubE = znoise_keypair:pubkey(E),
{mix_hash(HS#noise_hs{ e = E }, PubE), PubE};
%% Should only apply during test - TODO: secure this
write_token(HS = #noise_hs{ e = E }, e) ->
PubE = enoise_keypair:pubkey(E),
PubE = znoise_keypair:pubkey(E),
{mix_hash(HS, PubE), PubE};
write_token(HS = #noise_hs{ s = S }, s) ->
{ok, HS1, Msg} = encrypt_and_hash(HS, enoise_keypair:pubkey(S)),
{ok, HS1, Msg} = encrypt_and_hash(HS, znoise_keypair:pubkey(S)),
{HS1, Msg};
write_token(HS, Token) ->
{K1, K2} = dh_token(HS, Token),
{mix_key(HS, dh(HS, K1, K2)), <<>>}.
read_token(HS = #noise_hs{ re = undefined, dh = DH }, e, Data0) ->
DHLen = enoise_crypto:dhlen(DH),
DHLen = znoise_crypto:dhlen(DH),
case Data0 of
<<REPub:DHLen/binary, Data1/binary>> ->
RE = enoise_keypair:new(DH, REPub),
RE = znoise_keypair:new(DH, REPub),
{ok, mix_hash(HS#noise_hs{ re = RE }, REPub), Data1};
_ ->
{error, {bad_data, {failed_to_read_token, e, DHLen}}}
end;
read_token(HS = #noise_hs{ rs = undefined, dh = DH }, s, Data0) ->
DHLen = case has_key(HS) of
true -> enoise_crypto:dhlen(DH) + 16;
false -> enoise_crypto:dhlen(DH)
true -> znoise_crypto:dhlen(DH) + 16;
false -> znoise_crypto:dhlen(DH)
end,
case Data0 of
<<Temp:DHLen/binary, Data1/binary>> ->
case decrypt_and_hash(HS, Temp) of
{ok, HS1, RSPub} ->
RS = enoise_keypair:new(DH, RSPub),
RS = znoise_keypair:new(DH, RSPub),
{ok, HS1#noise_hs{ rs = RS }, Data1};
Err = {error, _} ->
Err
@@ -169,27 +173,27 @@ dh_token(#noise_hs{ s = S, rs = RS } , ss) -> {S, RS}.
%% Local wrappers
new_key_pair(#noise_hs{ dh = DH }) ->
enoise_keypair:new(DH).
znoise_keypair:new(DH).
dh(#noise_hs{ dh = DH }, Key1, Key2) ->
enoise_crypto:dh(DH, Key1, Key2).
znoise_crypto:dh(DH, Key1, Key2).
has_key(#noise_hs{ ss = SS }) ->
CS = enoise_sym_state:cipher_state(SS),
enoise_cipher_state:has_key(CS).
CS = znoise_sym_state:cipher_state(SS),
znoise_cipher_state:has_key(CS).
mix_key(HS = #noise_hs{ ss = SS0 }, Data) ->
HS#noise_hs{ ss = enoise_sym_state:mix_key(SS0, Data) }.
HS#noise_hs{ ss = znoise_sym_state:mix_key(SS0, Data) }.
mix_hash(HS = #noise_hs{ ss = SS0 }, Data) ->
HS#noise_hs{ ss = enoise_sym_state:mix_hash(SS0, Data) }.
HS#noise_hs{ ss = znoise_sym_state:mix_hash(SS0, Data) }.
encrypt_and_hash(HS = #noise_hs{ ss = SS0 }, PlainText) ->
{ok, SS1, CipherText} = enoise_sym_state:encrypt_and_hash(SS0, PlainText),
{ok, SS1, CipherText} = znoise_sym_state:encrypt_and_hash(SS0, PlainText),
{ok, HS#noise_hs{ ss = SS1 }, CipherText}.
decrypt_and_hash(HS = #noise_hs{ ss = SS0 }, CipherText) ->
case enoise_sym_state:decrypt_and_hash(SS0, CipherText) of
case znoise_sym_state:decrypt_and_hash(SS0, CipherText) of
{ok, SS1, PlainText} ->
{ok, HS#noise_hs{ ss = SS1 }, PlainText};
@@ -1,12 +1,16 @@
%%% ------------------------------------------------------------------
%%% @copyright 2026, QPQ AG
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc Module is an abstract data type for a key pair.
%%%
%%% @doc
%%% Module is an abstract data type for a key pair.
%%% @end
%%% ------------------------------------------------------------------
-module(enoise_keypair).
-module(znoise_keypair).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-author("Hans Svensson <hanssv@gmail.com>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([ key_type/1
, new/1
@@ -1,12 +1,16 @@
%%% ------------------------------------------------------------------
%%% @copyright 2026, QPQ AG
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc Module defining Noise protocol configurations
%%%
%%% @doc
%%% Module defining Noise protocol configurations
%%% @end
%%% ------------------------------------------------------------------
-module(enoise_protocol).
-module(znoise_protocol).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-author("Hans Svensson <hanssv@gmail.com>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([ cipher/1
, dh/1
@@ -23,28 +27,28 @@
-endif.
-type noise_pattern() :: nn | kn | nk | kk | nx | kx | xn | in | xk | ik | xx | ix.
-type noise_msg() :: {in | out, [enoise_hs_state:noise_token()]}.
-type noise_msg() :: {in | out, [znoise_hs_state:noise_token()]}.
-record(noise_protocol,
{ hs_pattern = noiseNN :: noise_pattern()
, dh = dh25519 :: enoise_hs_state:noise_dh()
, cipher = 'ChaChaPoly' :: enoise_cipher_state:noise_cipher()
, hash = blake2b :: enoise_sym_state:noise_hash()
, dh = dh25519 :: znoise_hs_state:noise_dh()
, cipher = 'ChaChaPoly' :: znoise_cipher_state:noise_cipher()
, hash = blake2b :: znoise_sym_state:noise_hash()
}).
-opaque protocol() :: #noise_protocol{}.
-export_type([noise_msg/0, noise_pattern/0, protocol/0]).
-spec cipher(Protocol :: protocol()) -> enoise_cipher_state:noise_cipher().
-spec cipher(Protocol :: protocol()) -> znoise_cipher_state:noise_cipher().
cipher(#noise_protocol{ cipher = Cipher }) ->
Cipher.
-spec dh(Protocol :: protocol()) -> enoise_hs_state:noise_dh().
-spec dh(Protocol :: protocol()) -> znoise_hs_state:noise_dh().
dh(#noise_protocol{ dh = Dh }) ->
Dh.
-spec hash(Protocol :: protocol()) -> enoise_sym_state:noise_hash().
-spec hash(Protocol :: protocol()) -> znoise_sym_state:noise_hash().
hash(#noise_protocol{ hash = Hash }) ->
Hash.
@@ -80,17 +84,17 @@ from_name(String) ->
error({name_not_recognized, String})
end.
-spec msgs(Role :: enoise_hs_state:noise_role(), Protocol :: protocol()) -> [noise_msg()].
-spec msgs(Role :: znoise_hs_state:noise_role(), Protocol :: protocol()) -> [noise_msg()].
msgs(Role, #noise_protocol{ hs_pattern = Pattern }) ->
{_Pre, Msgs} = protocol(Pattern),
role_adapt(Role, Msgs).
-spec pre_msgs(Role :: enoise_hs_state:noise_role(), Protocol :: protocol()) -> [noise_msg()].
-spec pre_msgs(Role :: znoise_hs_state:noise_role(), Protocol :: protocol()) -> [noise_msg()].
pre_msgs(Role, #noise_protocol{ hs_pattern = Pattern }) ->
{PreMsgs, _Msgs} = protocol(Pattern),
role_adapt(Role, PreMsgs).
-spec role_adapt(Role :: enoise_hs_state:noise_role(), [noise_msg()]) -> [noise_msg()].
-spec role_adapt(Role :: znoise_hs_state:noise_role(), [noise_msg()]) -> [noise_msg()].
role_adapt(initiator, Msgs) ->
Msgs;
role_adapt(responder, Msgs) ->
@@ -1,12 +1,15 @@
%%% ------------------------------------------------------------------
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc Module encapsulating a Noise symmetric (hash) state
%%%
%%% @doc
%%% Module encapsulating a Noise symmetric (hash) state
%%% @end
%%% ------------------------------------------------------------------
-module(enoise_sym_state).
-module(znoise_sym_state).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-author("Hans Svensson <hanssv@gmail.com>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([ cipher_state/1
, ck/1
@@ -21,11 +24,11 @@
, split/1
]).
-include("enoise.hrl").
-include("znoise.hrl").
-type noise_hash() :: sha256 | sha512 | blake2s | blake2b.
-record(noise_ss, { cs :: enoise_cipher_state:state()
-record(noise_ss, { cs :: znoise_cipher_state:state()
, ck = <<>> :: binary()
, h = <<>> :: binary()
, hash = blake2b :: noise_hash() }).
@@ -33,64 +36,64 @@
-opaque state() :: #noise_ss{}.
-export_type([noise_hash/0, state/0]).
-spec init(Protocol :: enoise_protocol:protocol()) -> state().
-spec init(Protocol :: znoise_protocol:protocol()) -> state().
init(Protocol) ->
Hash = enoise_protocol:hash(Protocol),
Cipher = enoise_protocol:cipher(Protocol),
Name = enoise_protocol:to_name(Protocol),
HashLen = enoise_crypto:hashlen(Hash),
Hash = znoise_protocol:hash(Protocol),
Cipher = znoise_protocol:cipher(Protocol),
Name = znoise_protocol:to_name(Protocol),
HashLen = znoise_crypto:hashlen(Hash),
H1 =
case byte_size(Name) > HashLen of
true -> enoise_crypto:hash(Hash, Name);
false -> enoise_crypto:pad(Name, HashLen, 16#00)
true -> znoise_crypto:hash(Hash, Name);
false -> znoise_crypto:pad(Name, HashLen, 16#00)
end,
#noise_ss{ h = H1
, ck = H1
, hash = Hash
, cs = enoise_cipher_state:init(empty, Cipher) }.
, cs = znoise_cipher_state:init(empty, Cipher) }.
-spec mix_key(SState :: state(), InputKeyMaterial :: binary()) -> state().
mix_key(SState = #noise_ss{ hash = Hash, ck = CK0, cs = CS0 }, InputKeyMaterial) ->
[CK1, <<TempK:32/binary, _/binary>> | _] =
enoise_crypto:hkdf(Hash, CK0, InputKeyMaterial),
CS1 = enoise_cipher_state:set_key(CS0, TempK),
znoise_crypto:hkdf(Hash, CK0, InputKeyMaterial),
CS1 = znoise_cipher_state:set_key(CS0, TempK),
SState#noise_ss{ ck = CK1, cs = CS1 }.
-spec mix_hash(SState :: state(), Data :: binary()) -> state().
mix_hash(SState = #noise_ss{ hash = Hash, h = H0 }, Data) ->
H1 = enoise_crypto:hash(Hash, <<H0/binary, Data/binary>>),
H1 = znoise_crypto:hash(Hash, <<H0/binary, Data/binary>>),
SState#noise_ss{ h = H1 }.
-spec mix_key_and_hash(SState :: state(), InputKeyMaterial :: binary()) -> state().
mix_key_and_hash(SState = #noise_ss{ hash = Hash, ck = CK0, cs = CS0 }, InputKeyMaterial) ->
[CK1, TempH, <<TempK:32/binary, _/binary>>] =
enoise_crypto:hkdf(Hash, CK0, InputKeyMaterial),
CS1 = enoise_cipher_state:set_key(CS0, TempK),
znoise_crypto:hkdf(Hash, CK0, InputKeyMaterial),
CS1 = znoise_cipher_state:set_key(CS0, TempK),
mix_hash(SState#noise_ss{ ck = CK1, cs = CS1 }, TempH).
-spec encrypt_and_hash(SState :: state(), PlainText :: binary()) -> {ok, state(), binary()}.
encrypt_and_hash(SState = #noise_ss{ cs = CS0, h = H }, PlainText) ->
{ok, CS1, CipherText} = enoise_cipher_state:encrypt_with_ad(CS0, H, PlainText),
{ok, CS1, CipherText} = znoise_cipher_state:encrypt_with_ad(CS0, H, PlainText),
{ok, mix_hash(SState#noise_ss{ cs = CS1 }, CipherText), CipherText}.
-spec decrypt_and_hash(SState :: state(), CipherText :: binary()) ->
{ok, state(), binary()} | {error, term()}.
decrypt_and_hash(SState = #noise_ss{ cs = CS0, h = H }, CipherText) ->
case enoise_cipher_state:decrypt_with_ad(CS0, H, CipherText) of
case znoise_cipher_state:decrypt_with_ad(CS0, H, CipherText) of
Err = {error, _} ->
Err;
{ok, CS1, PlainText} ->
{ok, mix_hash(SState#noise_ss{ cs = CS1 }, CipherText), PlainText}
end.
-spec split(SState :: state()) -> {enoise_cipher_state:state(), enoise_cipher_state:state()}.
-spec split(SState :: state()) -> {znoise_cipher_state:state(), znoise_cipher_state:state()}.
split(#noise_ss{ hash = Hash, ck = CK, cs = CS }) ->
[<<TempK1:32/binary, _/binary>>, <<TempK2:32/binary, _/binary>>, _] =
enoise_crypto:hkdf(Hash, CK, <<>>),
{enoise_cipher_state:set_key(CS, TempK1),
enoise_cipher_state:set_key(CS, TempK2)}.
znoise_crypto:hkdf(Hash, CK, <<>>),
{znoise_cipher_state:set_key(CS, TempK1),
znoise_cipher_state:set_key(CS, TempK2)}.
-spec cipher_state(SState :: state()) -> enoise_cipher_state:state().
-spec cipher_state(SState :: state()) -> znoise_cipher_state:state().
cipher_state(#noise_ss{ cs = CS }) ->
CS.
+243
View File
@@ -0,0 +1,243 @@
%%% @copyright 2026, QPQ AG
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc
%%% A gen_server for holding a Noise connection over gen_tcp.
%%%
%%% Some care is needed since the underlying transmission is broken up
%%% into Noise packets, so we need some buffering.
%%% @end
-module(znoise_tcp).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-author("Hans Svensson <hanssv@gmail.com>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([controlling_process/2,
close/1,
send/2,
set_active/2,
start_link/5]).
%% gen_server
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(znoise,
{pid}).
-record(s,
{rx = ,
tx = ,
owner = none :: none | pid(),
owner_ref = none :: none | reference(),
tcp_sock = none :: none | gen_tcp:socket(),
active = once :: true | {once, boolean()},
msgbuf = [] :: list(),
rawbuf = <<>> :: binary()}).
start_link(TcpSock, Rx, Tx, Owner, {Active0, Buf}) ->
Active =
case Active0 of
true -> true;
once -> {once, false}
end,
State =
#s{rx = Rx,
tx = Tx,
owner = Owner,
tcp_sock = TcpSock,
active = Active},
case gen_server:start_link(?MODULE, [State], []) of
{ok, Pid} ->
case gen_tcp:controlling_process(TcpSock, Pid) of
ok ->
% Changing controlling process require a bit of
% fiddling with already received and delivered content...
ok =
case Buf =/= <<>> of
true -> Pid ! {tcp, TcpSock, Buf};
false -> ok
end,
flush_tcp(Pid, TcpSock),
{ok, Pid};
Error ->
close(Pid),
Error
end;
Error
Error
end.
-spec send(Noise :: pid(), Data :: binary()) -> ok | {error, term()}.
send(Noise, Data) ->
gen_server:call(Noise, {send, Data}).
-spec set_active(Noise :: pid(), Active :: true | once) -> ok | {error, term()}.
set_active(Noise, Active) ->
gen_server:call(Noise, {active, self(), Active}).
-spec close(Noise :: pid()) -> ok | {error, term()}.
close(Noise) ->
gen_server:call(Noise, close).
-spec controlling_process(Noise :: pid(), NewPid :: pid()) -> ok | {error, term()}.
controlling_process(Noise, NewPid) ->
gen_server:call(Noise, {controlling_process, self(), NewPid}, 100).
%% gen_server
init([#s{owner = Owner} = State]) ->
OwnerRef = erlang:monitor(process, Owner),
{ok, State#s{owner_ref = OwnerRef}}.
handle_call(close, _, State) ->
{stop, normal, ok, State};
handle_call(_Call, _, State = #s{tcp_sock = closed}) ->
{reply, {error, closed}, State};
handle_call({send, Data}, _, State) ->
{Result, NewState} = handle_send(State, Data),
{reply, Result, NewState};
handle_call({controlling_process, OldPID, NewPID}, _, State) ->
{Result, NewState} = handle_control_change(State, OldPID, NewPID),
{reply, Result, NewState};
handle_call({active, PID, NewActive}, _, State) ->
{Result, NewState} = handle_active(State, PID, NewActive),
{reply, Result, NewState}.
handle_cast(_, State) ->
{noreply, State}.
handle_info({tcp, TS, Data}, State = #s{tcp_sock = TS, owner = O}) ->
try
{NextState = #s{msgbuf = Buf}, Msgs} = handle_data(State, Data),
NewState = handle_msgs(NextState#s{msgbuf = Buf ++ Msgs}),
set_active(NewState),
{noreply, NewState}
catch error:{znoise_error, _} ->
%% We are not likely to recover, but leave the decision to upstream
O ! {znoise_error, TS, decrypt_error},
{noreply, State}
end;
handle_info({tcp_closed, TS}, State = #s{tcp_sock = TS, owner = O}) ->
O ! {tcp_closed, TS},
{noreply, State#s{tcp_sock = closed}};
handle_info({'DOWN', OwnerRef, process, _, normal},
State = #s{tcp_sock = TS, owner_ref = OwnerRef}) ->
close_tcp(TS),
{stop, normal, State#s{tcp_sock = closed, owner_ref = undefined}};
handle_info({'DOWN', _, _, _, _}, State) ->
%% Ignore non-normal monitor messages - we are linked.
{noreply, State};
handle_info(_Msg, State) ->
{noreply, State}.
terminate(_, #s{tcp_sock = TcpSock, owner_ref = ORef}) ->
[ gen_tcp:close(TcpSock) || TcpSock /= closed ],
[ erlang:demonitor(ORef, [flush]) || ORef /= undefined ],
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%% Handlers
handle_control_change(State = #s{owner = PID, owner_ref = OldRef}, PID, NewPID) ->
NewRef = erlang:monitor(process, NewPID),
erlang:demonitor(OldRef, [flush]),
{ok, State#s{owner = NewPID, owner_ref = NewRef}};
handle_control_change(State, _, _) ->
{{error, not_owner}, State}.
handle_active(State = #s{owner = PID, tcp_sock = TcpSock}, PID, Active) ->
case Active of
true ->
inet:setopts(TcpSock, [{active, true}]),
{ok, handle_msgs(State#s{active = true})};
once ->
NewState = handle_msgs(State#s{active = {once, false}}),
set_active(NewState),
{ok, NewState}
end;
handle_active(State, _, _) ->
{{error, not_owner}, State}.
handle_data(State = #s{rawbuf = Buf, rx = RX}, Data) ->
case <<Buf/binary, Data/binary>> of
B = <<Len:16, Rest/binary>> when Len > byte_size(Rest) ->
{State#s{rawbuf = B}, []}; %% Not a full Noise message - save it
<<Len:16, Rest/binary>> ->
<<Msg:Len/binary, Rest2/binary>> = Rest,
case znoise_cipher_state:decrypt_with_ad(RX, <<>>, Msg) of
{ok, NewRX, NewMsg} ->
{NewState, Msgs} = handle_data(State#s{rawbuf = Rest2, rx = NewRX}, <<>>),
{NewState, [NewMsg | Msgs]};
{error, _} ->
error({znoise_error, decrypt_input_failed})
end;
EmptyOrSingleByte ->
{State#s{rawbuf = EmptyOrSingleByte}, []}
end.
handle_msgs(State = #s{msgbuf = []}) ->
State;
handle_msgs(State = #s{msgbuf = Msgs, active = true, owner = Owner}) ->
[ Owner ! {noise, #znoise{pid = self()}, Msg} || Msg <- Msgs ],
State#s{msgbuf = []};
handle_msgs(State = #s{msgbuf = [Msg | Msgs], active = {once, Delivered}, owner = Owner}) ->
case Delivered of
true ->
State;
false ->
Owner ! {noise, #znoise{pid = self()}, Msg},
State#s{msgbuf = Msgs, active = {once, true}}
end.
handle_send(State = #s{tcp_sock = TcpSock, tx = TX}, Data) ->
{ok, MewTX, Msg} = znoise_cipher_state:encrypt_with_ad(TX, <<>>, Data),
case gen_tcp:send(TcpSock, <<(byte_size(Msg)):16, Msg/binary>>) of
ok -> {ok, State#s{tx = MewTX}};
Error -> {Error, State}
end.
set_active(#s{msgbuf = [], active = {once, _}, tcp_sock = TcpSock}) ->
inet:setopts(TcpSock, [{active, once}]);
set_active(_) ->
ok.
flush_tcp(Pid, TcpSock) ->
receive {tcp, TcpSock, Data} ->
Pid ! {tcp, TcpSock, Data},
flush_tcp(Pid, TcpSock)
after 1 -> ok
end.
close_tcp(closed) ->
ok;
close_tcp(Sock) ->
gen_tcp:close(Sock).
-56
View File
@@ -1,56 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(enoise_sym_state_tests).
-include_lib("eunit/include/eunit.hrl").
noise_XK_25519_ChaChaPoly_Blake2b_test() ->
Protocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
SSE0 = enoise_sym_state:init(Protocol),
SSD0 = enoise_sym_state:init(Protocol),
Name = enoise_protocol:to_name(Protocol),
PadName = enoise_crypto:pad(Name, enoise_crypto:hashlen(blake2b), 0),
?assertMatch(PadName, enoise_sym_state:h(SSE0)),
?assertMatch(PadName, enoise_sym_state:ck(SSE0)),
?assertMatch(false, enoise_cipher_state:has_key(enoise_sym_state:cipher_state(SSE0))),
TestBin = h2b("0x6162636465666768696A6B6C6D6E6F707172737475767778797A"),
SSE1 = enoise_sym_state:mix_hash(SSE0, TestBin),
SSD1 = enoise_sym_state:mix_hash(SSD0, TestBin),
ExpHash1 = enoise_crypto:hash(blake2b, <<PadName/binary, TestBin/binary>>),
ExpHash2 = h2b("0x8DC23DE176F6B3581FB7E18F258A47B1E1A8090BF55978868F1AC88C672DC3918FA4D1828338FB5DF652F5C33D57C79537CB5D074057EF59C346D0B35A160F71"),
?assertMatch(ExpHash1, enoise_sym_state:h(SSE1)),
?assertMatch(ExpHash2, enoise_sym_state:h(SSD1)),
{ok, SSE2, TestBin} = enoise_sym_state:encrypt_and_hash(SSE1, TestBin),
{ok, SSD2, TestBin} = enoise_sym_state:decrypt_and_hash(SSD1, TestBin),
SSE3 = enoise_sym_state:mix_key(SSE2, TestBin),
SSD3 = enoise_sym_state:mix_key(SSD2, TestBin),
ExpEncrypt = h2b("0x24FB13758E6BA9901A4CEA117AE1D9AF757B02CAE96EFDFDA5ED3927BDD9FEA0239F7F673E924AAE81E6"),
{ok, SSE4, Encrypt} = enoise_sym_state:encrypt_and_hash(SSE3, TestBin),
?assertMatch(ExpEncrypt, Encrypt),
{ok, SSD4, Decrypt} = enoise_sym_state:decrypt_and_hash(SSD3, ExpEncrypt),
?assertMatch(TestBin, Decrypt),
Key1 = h2b("0x893FD190EDB611D9AF73868C8AB020F7A13C62F70F7F74C46859CF4A1E71BB74"),
Key2 = h2b("0x492E210AD0181CE70BF9CE80308DE45EAE1FA76E1ACE22A829EF6F1A01C6E2C8"),
{CSE1, CSE2} = enoise_sym_state:split(SSE4),
?assertMatch(Key1, enoise_cipher_state:key(CSE1)),
?assertMatch(Key2, enoise_cipher_state:key(CSE2)),
{CSD1, CSD2} = enoise_sym_state:split(SSD4),
?assertMatch(Key1, enoise_cipher_state:key(CSD1)),
?assertMatch(Key2, enoise_cipher_state:key(CSD2)),
ok.
h2b(S) -> test_utils:hex_str_to_bin(S).
+1 -1
View File
@@ -123,5 +123,5 @@ noise_test_filter(Tests0) ->
Tests1.
supported(Name) ->
try enoise_protocol:from_name(Name), true
try znoise_protocol:from_name(Name), true
catch _:_ -> false end.
@@ -1,15 +1,15 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(enoise_bad_data_tests).
-module(znoise_bad_data_tests).
-include_lib("eunit/include/eunit.hrl").
bad_data_hs_1_test() ->
SrvKeyPair = enoise_keypair:new(dh25519),
Proto = enoise_protocol:to_name(xk, dh25519, 'ChaChaPoly', blake2b),
SrvKeyPair = znoise_keypair:new(dh25519),
Proto = znoise_protocol:to_name(xk, dh25519, 'ChaChaPoly', blake2b),
Opts = [{echos, 1}, {reply, self()}],
Srv = enoise_utils:echo_srv_start(4567, Proto, SrvKeyPair, Opts),
Srv = znoise_utils:echo_srv_start(4567, Proto, SrvKeyPair, Opts),
bad_client(4567),
@@ -2,7 +2,7 @@
%%% @copyright (C) 2018, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(enoise_chiper_state_tests).
-module(znoise_chiper_state_tests).
-include_lib("eunit/include/eunit.hrl").
@@ -13,26 +13,26 @@ chachapoly_test() ->
CTLen = byte_size(CipherText),
MACLen = byte_size(MAC),
CS0 = enoise_cipher_state:init(Key, 'ChaChaPoly'),
CS1 = enoise_cipher_state:set_nonce(CS0, Nonce),
CS0 = znoise_cipher_state:init(Key, 'ChaChaPoly'),
CS1 = znoise_cipher_state:set_nonce(CS0, Nonce),
{ok, _CS2, <<CipherText0:CTLen/binary, MAC0:MACLen/binary>>} =
enoise_cipher_state:encrypt_with_ad(CS1, AD, PlainText),
znoise_cipher_state:encrypt_with_ad(CS1, AD, PlainText),
?assertMatch(CipherText, CipherText0),
?assertMatch(MAC, MAC0),
{ok, _CS3, <<PlainText0:PTLen/binary>>} =
enoise_cipher_state:decrypt_with_ad(CS1, AD, <<CipherText/binary, MAC/binary>>),
znoise_cipher_state:decrypt_with_ad(CS1, AD, <<CipherText/binary, MAC/binary>>),
?assertMatch(PlainText, PlainText0),
% rekey test
CS4 = enoise_cipher_state:rekey(CS1),
CS4 = znoise_cipher_state:rekey(CS1),
{ok, _CS5, <<CipherText1:CTLen/binary, MAC1:MACLen/binary>>} =
enoise_cipher_state:encrypt_with_ad(CS4, AD, PlainText),
znoise_cipher_state:encrypt_with_ad(CS4, AD, PlainText),
{ok, _CS6, <<PlainText1:PTLen/binary>>} =
enoise_cipher_state:decrypt_with_ad(CS4, AD, <<CipherText1/binary, MAC1/binary>>),
znoise_cipher_state:decrypt_with_ad(CS4, AD, <<CipherText1/binary, MAC1/binary>>),
?assertMatch(PlainText, PlainText1),
ok.
@@ -2,25 +2,25 @@
%%% @copyright (C) 2018, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(enoise_crypto_tests).
-module(znoise_crypto_tests).
-include_lib("eunit/include/eunit.hrl").
curve25519_test() ->
KeyPair1 = enoise_keypair:new(dh25519),
KeyPair2 = enoise_keypair:new(dh25519),
KeyPair1 = znoise_keypair:new(dh25519),
KeyPair2 = znoise_keypair:new(dh25519),
SharedA = enoise_crypto:dh(dh25519, KeyPair1, KeyPair2),
SharedB = enoise_crypto:dh(dh25519, KeyPair2, KeyPair1),
SharedA = znoise_crypto:dh(dh25519, KeyPair1, KeyPair2),
SharedB = znoise_crypto:dh(dh25519, KeyPair2, KeyPair1),
?assertMatch(SharedA, SharedB),
#{ a_pub := APub, a_priv := APriv,
b_pub := BPub, b_priv := BPriv, shared := Shared } = test_utils:curve25519_data(),
KeyPair3 = enoise_keypair:new(dh25519, APriv, APub),
KeyPair4 = enoise_keypair:new(dh25519, BPriv, BPub),
?assertMatch(Shared, enoise_crypto:dh(dh25519, KeyPair3, KeyPair4)),
?assertMatch(Shared, enoise_crypto:dh(dh25519, KeyPair4, KeyPair3)),
KeyPair3 = znoise_keypair:new(dh25519, APriv, APub),
KeyPair4 = znoise_keypair:new(dh25519, BPriv, BPub),
?assertMatch(Shared, znoise_crypto:dh(dh25519, KeyPair3, KeyPair4)),
?assertMatch(Shared, znoise_crypto:dh(dh25519, KeyPair4, KeyPair3)),
ok.
@@ -35,43 +35,43 @@ chachapoly_test() ->
?assert(PTLen == CTLen),
<<CipherText0:CTLen/binary, MAC0:MACLen/binary>> =
enoise_crypto:encrypt('ChaChaPoly', Key, Nonce, AD, PlainText),
znoise_crypto:encrypt('ChaChaPoly', Key, Nonce, AD, PlainText),
?assertMatch(CipherText, CipherText0),
?assertMatch(MAC, MAC0),
<<PlainText0:PTLen/binary>> =
enoise_crypto:decrypt('ChaChaPoly', Key, Nonce, AD, <<CipherText/binary, MAC/binary>>),
znoise_crypto:decrypt('ChaChaPoly', Key, Nonce, AD, <<CipherText/binary, MAC/binary>>),
?assertMatch(PlainText, PlainText0),
Key1 = enoise_crypto:rekey('ChaChaPoly', Key),
Key1 = znoise_crypto:rekey('ChaChaPoly', Key),
<<CipherText1:CTLen/binary, MAC1:MACLen/binary>> =
enoise_crypto:encrypt('ChaChaPoly', Key1, Nonce, AD, PlainText),
znoise_crypto:encrypt('ChaChaPoly', Key1, Nonce, AD, PlainText),
<<PlainText1:PTLen/binary>> =
enoise_crypto:decrypt('ChaChaPoly', Key1, Nonce, AD, <<CipherText1/binary, MAC1/binary>>),
znoise_crypto:decrypt('ChaChaPoly', Key1, Nonce, AD, <<CipherText1/binary, MAC1/binary>>),
?assertMatch(PlainText, PlainText1),
ok.
blake2b_test() ->
Test = fun(#{ input := In, output := Out }) ->
?assertMatch(Out, enoise_crypto:hash(blake2b, In))
?assertMatch(Out, znoise_crypto:hash(blake2b, In))
end,
lists:foreach(Test, test_utils:blake2b_data()).
%% blake2s_test() ->
%% #{ input := In, output := Out } = test_utils:blake2s_data(),
%% ?assertMatch(Out, enoise_crypto:hash(blake2s, In)).
%% ?assertMatch(Out, znoise_crypto:hash(blake2s, In)).
blake2b_hmac_test() ->
Test = fun(#{ key := Key, data := Data, hmac := HMAC }) ->
?assertMatch(HMAC, enoise_crypto:hmac(blake2b, Key, Data))
?assertMatch(HMAC, znoise_crypto:hmac(blake2b, Key, Data))
end,
lists:foreach(Test, test_utils:blake2b_hmac_data()).
blake2b_hkdf_test() ->
Test = fun(#{ key := Key, data := Data, out1 := Out1, out2 := Out2 }) ->
?assertMatch([Out1, Out2, _], enoise_crypto:hkdf(blake2b, Key, Data))
?assertMatch([Out1, Out2, _], znoise_crypto:hkdf(blake2b, Key, Data))
end,
lists:foreach(Test, test_utils:blake2b_hkdf_data()).
@@ -2,7 +2,7 @@
%%% @copyright (C) 2018, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(enoise_hs_state_tests).
-module(znoise_hs_state_tests).
-include_lib("eunit/include/eunit.hrl").
@@ -18,7 +18,7 @@ noise_hs_test_() ->
}.
noise_hs_test(V = #{ protocol_name := Name }) ->
Protocol = enoise_protocol:from_name(Name),
Protocol = znoise_protocol:from_name(Name),
FixK = fun(undefined) -> undefined;
(Bin) -> test_utils:hex_str_to_bin("0x" ++ binary_to_list(Bin)) end,
@@ -39,11 +39,11 @@ noise_hs_test(V = #{ protocol_name := Name }) ->
ok.
noise_test(_Name, Protocol, Init, Resp, Messages, HSHash) ->
DH = enoise_protocol:dh(Protocol),
SecK = fun(undefined) -> undefined; (Sec) -> enoise_keypair:new(DH, Sec, undefined) end,
PubK = fun(undefined) -> undefined; (Pub) -> enoise_keypair:new(DH, Pub) end,
DH = znoise_protocol:dh(Protocol),
SecK = fun(undefined) -> undefined; (Sec) -> znoise_keypair:new(DH, Sec, undefined) end,
PubK = fun(undefined) -> undefined; (Pub) -> znoise_keypair:new(DH, Pub) end,
HSInit = fun(P, R, #{ e := E, s := S, rs := RS, prologue := PL }) ->
{ok, HS} = enoise_hs_state:init(P, R, PL, {SecK(S), SecK(E), PubK(RS), undefined}),
{ok, HS} = znoise_hs_state:init(P, R, PL, {SecK(S), SecK(E), PubK(RS), undefined}),
HS
end,
@@ -57,16 +57,16 @@ noise_test(_Name, Protocol, Init, Resp, Messages, HSHash) ->
noise_test([M = #{ payload := PL0, ciphertext := CT0 } | Msgs], SendHS, RecvHS, HSHash) ->
PL = test_utils:hex_str_to_bin("0x" ++ binary_to_list(PL0)),
CT = test_utils:hex_str_to_bin("0x" ++ binary_to_list(CT0)),
case {enoise_hs_state:next_message(SendHS), enoise_hs_state:next_message(RecvHS)} of
case {znoise_hs_state:next_message(SendHS), znoise_hs_state:next_message(RecvHS)} of
{out, in} ->
{ok, SendHS1, Message} = enoise_hs_state:write_message(SendHS, PL),
{ok, SendHS1, Message} = znoise_hs_state:write_message(SendHS, PL),
?assertEqual(CT, Message),
{ok, RecvHS1, PL1} = enoise_hs_state:read_message(RecvHS, Message),
{ok, RecvHS1, PL1} = znoise_hs_state:read_message(RecvHS, Message),
?assertEqual(PL, PL1),
noise_test(Msgs, RecvHS1, SendHS1, HSHash);
{done, done} ->
{ok, #{ rx := RX1, tx := TX1, hs_hash := HSHash1 }} = enoise_hs_state:finalize(SendHS),
{ok, #{ rx := RX2, tx := TX2, hs_hash := HSHash2 }} = enoise_hs_state:finalize(RecvHS),
{ok, #{ rx := RX1, tx := TX1, hs_hash := HSHash1 }} = znoise_hs_state:finalize(SendHS),
{ok, #{ rx := RX2, tx := TX2, hs_hash := HSHash2 }} = znoise_hs_state:finalize(RecvHS),
?assertEqual(RX1, TX2), ?assertEqual(RX2, TX1),
?assertEqual(HSHash, HSHash1), ?assertEqual(HSHash, HSHash2),
noise_test([M | Msgs], TX1, RX1);
@@ -77,9 +77,9 @@ noise_test([], _, _) -> ok;
noise_test([#{ payload := PL0, ciphertext := CT0 } | Msgs], CA, CB) ->
PL = test_utils:hex_str_to_bin("0x" ++ binary_to_list(PL0)),
CT = test_utils:hex_str_to_bin("0x" ++ binary_to_list(CT0)),
{ok, CA1, CT1} = enoise_cipher_state:encrypt_with_ad(CA, <<>>, PL),
{ok, CA1, CT1} = znoise_cipher_state:encrypt_with_ad(CA, <<>>, PL),
?assertEqual(CT, CT1),
{ok, CA2, PL1} = enoise_cipher_state:decrypt_with_ad(CA, <<>>, CT1),
{ok, CA2, PL1} = znoise_cipher_state:decrypt_with_ad(CA, <<>>, CT1),
?assertEqual(CA1, CA2),
?assertEqual(PL, PL1),
noise_test(Msgs, CB, CA1).
@@ -2,7 +2,7 @@
%%% @copyright (C) 2018, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(enoise_protocol_tests).
-module(znoise_protocol_tests).
-include_lib("eunit/include/eunit.hrl").
@@ -12,13 +12,13 @@ name_test() ->
name2_test() ->
Name = "Noise_NXpsk2_25519_AESGCM_SHA512",
?assertError({name_not_recognized, Name}, enoise_protocol:from_name(Name)).
?assertError({name_not_recognized, Name}, znoise_protocol:from_name(Name)).
name_pattern_test() ->
Pat = "XKfallback+psk0",
RoundPat = enoise_protocol:to_name_pattern(enoise_protocol:from_name_pattern(Pat)),
RoundPat = znoise_protocol:to_name_pattern(znoise_protocol:from_name_pattern(Pat)),
?assertEqual(Pat, RoundPat).
roundtrip(Name) ->
ExpectedName = iolist_to_binary(Name),
?assertMatch(ExpectedName, enoise_protocol:to_name(enoise_protocol:from_name(Name))).
?assertMatch(ExpectedName, znoise_protocol:to_name(znoise_protocol:from_name(Name))).
+56
View File
@@ -0,0 +1,56 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(znoise_sym_state_tests).
-include_lib("eunit/include/eunit.hrl").
noise_XK_25519_ChaChaPoly_Blake2b_test() ->
Protocol = znoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
SSE0 = znoise_sym_state:init(Protocol),
SSD0 = znoise_sym_state:init(Protocol),
Name = znoise_protocol:to_name(Protocol),
PadName = znoise_crypto:pad(Name, znoise_crypto:hashlen(blake2b), 0),
?assertMatch(PadName, znoise_sym_state:h(SSE0)),
?assertMatch(PadName, znoise_sym_state:ck(SSE0)),
?assertMatch(false, znoise_cipher_state:has_key(znoise_sym_state:cipher_state(SSE0))),
TestBin = h2b("0x6162636465666768696A6B6C6D6E6F707172737475767778797A"),
SSE1 = znoise_sym_state:mix_hash(SSE0, TestBin),
SSD1 = znoise_sym_state:mix_hash(SSD0, TestBin),
ExpHash1 = znoise_crypto:hash(blake2b, <<PadName/binary, TestBin/binary>>),
ExpHash2 = h2b("0x8DC23DE176F6B3581FB7E18F258A47B1E1A8090BF55978868F1AC88C672DC3918FA4D1828338FB5DF652F5C33D57C79537CB5D074057EF59C346D0B35A160F71"),
?assertMatch(ExpHash1, znoise_sym_state:h(SSE1)),
?assertMatch(ExpHash2, znoise_sym_state:h(SSD1)),
{ok, SSE2, TestBin} = znoise_sym_state:encrypt_and_hash(SSE1, TestBin),
{ok, SSD2, TestBin} = znoise_sym_state:decrypt_and_hash(SSD1, TestBin),
SSE3 = znoise_sym_state:mix_key(SSE2, TestBin),
SSD3 = znoise_sym_state:mix_key(SSD2, TestBin),
ExpEncrypt = h2b("0x24FB13758E6BA9901A4CEA117AE1D9AF757B02CAE96EFDFDA5ED3927BDD9FEA0239F7F673E924AAE81E6"),
{ok, SSE4, Encrypt} = znoise_sym_state:encrypt_and_hash(SSE3, TestBin),
?assertMatch(ExpEncrypt, Encrypt),
{ok, SSD4, Decrypt} = znoise_sym_state:decrypt_and_hash(SSD3, ExpEncrypt),
?assertMatch(TestBin, Decrypt),
Key1 = h2b("0x893FD190EDB611D9AF73868C8AB020F7A13C62F70F7F74C46859CF4A1E71BB74"),
Key2 = h2b("0x492E210AD0181CE70BF9CE80308DE45EAE1FA76E1ACE22A829EF6F1A01C6E2C8"),
{CSE1, CSE2} = znoise_sym_state:split(SSE4),
?assertMatch(Key1, znoise_cipher_state:key(CSE1)),
?assertMatch(Key2, znoise_cipher_state:key(CSE2)),
{CSD1, CSD2} = znoise_sym_state:split(SSD4),
?assertMatch(Key1, znoise_cipher_state:key(CSD1)),
?assertMatch(Key2, znoise_cipher_state:key(CSD2)),
ok.
h2b(S) -> test_utils:hex_str_to_bin(S).
+39 -39
View File
@@ -2,7 +2,7 @@
%%% @copyright (C) 2018, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(enoise_tests).
-module(znoise_tests).
-include_lib("eunit/include/eunit.hrl").
@@ -18,7 +18,7 @@ noise_interactive_test_() ->
}.
noise_interactive(V = #{ protocol_name := Name }) ->
Protocol = enoise_protocol:from_name(Name),
Protocol = znoise_protocol:from_name(Name),
FixK = fun(undefined) -> undefined;
(Bin) -> test_utils:hex_str_to_bin("0x" ++ binary_to_list(Bin)) end,
@@ -39,12 +39,12 @@ noise_interactive(V = #{ protocol_name := Name }) ->
ok.
noise_interactive(_Name, Protocol, Init, Resp, Messages, HSHash) ->
DH = enoise_protocol:dh(Protocol),
SecK = fun(undefined) -> undefined; (Sec) -> enoise_keypair:new(DH, Sec, undefined) end,
DH = znoise_protocol:dh(Protocol),
SecK = fun(undefined) -> undefined; (Sec) -> znoise_keypair:new(DH, Sec, undefined) end,
HSInit = fun(#{ e := E, s := S, rs := RS, prologue := PL }, R) ->
Opts = [{noise, Protocol}, {s, SecK(S)}, {e, SecK(E)}, {rs, RS}, {prologue, PL}],
enoise:handshake(Opts, R)
znoise:handshake(Opts, R)
end,
{ok, InitHS} = HSInit(Init, initiator),
{ok, RespHS} = HSInit(Resp, responder),
@@ -54,16 +54,16 @@ noise_interactive(_Name, Protocol, Init, Resp, Messages, HSHash) ->
noise_interactive([#{ payload := PL0, ciphertext := CT0 } | Msgs], SendHS, RecvHS, HSHash) ->
PL = test_utils:hex_str_to_bin("0x" ++ binary_to_list(PL0)),
CT = test_utils:hex_str_to_bin("0x" ++ binary_to_list(CT0)),
case enoise_hs_state:next_message(SendHS) of
case znoise_hs_state:next_message(SendHS) of
out ->
{ok, send, Message, SendHS1} = enoise:step_handshake(SendHS, {send, PL}),
{ok, send, Message, SendHS1} = znoise:step_handshake(SendHS, {send, PL}),
?assertEqual(CT, Message),
{ok, rcvd, PL1, RecvHS1} = enoise:step_handshake(RecvHS, {rcvd, Message}),
{ok, rcvd, PL1, RecvHS1} = znoise:step_handshake(RecvHS, {rcvd, Message}),
?assertEqual(PL, PL1),
noise_interactive(Msgs, RecvHS1, SendHS1, HSHash);
done ->
{ok, done, #{ rx := RX1, tx := TX1, hs_hash := HSHash1 }} = enoise:step_handshake(SendHS, done),
{ok, done, #{ rx := RX2, tx := TX2, hs_hash := HSHash2 }} = enoise:step_handshake(RecvHS, done),
{ok, done, #{ rx := RX1, tx := TX1, hs_hash := HSHash1 }} = znoise:step_handshake(SendHS, done),
{ok, done, #{ rx := RX2, tx := TX2, hs_hash := HSHash2 }} = znoise:step_handshake(RecvHS, done),
?assertEqual(RX1, TX2), ?assertEqual(RX2, TX1),
?assertEqual(HSHash, HSHash1), ?assertEqual(HSHash, HSHash2)
end.
@@ -89,19 +89,19 @@ noise_monitor_test_() ->
setup_dh25519() ->
%% Generate a static key-pair for Client and Server
SrvKeyPair = enoise_keypair:new(dh25519),
CliKeyPair = enoise_keypair:new(dh25519),
SrvKeyPair = znoise_keypair:new(dh25519),
CliKeyPair = znoise_keypair:new(dh25519),
#{ hs_pattern := Ps, hash := Hs, cipher := Cs } = enoise_protocol:supported(),
Configurations = [ enoise_protocol:to_name(P, dh25519, C, H)
#{ hs_pattern := Ps, hash := Hs, cipher := Cs } = znoise_protocol:supported(),
Configurations = [ znoise_protocol:to_name(P, dh25519, C, H)
|| P <- Ps, C <- Cs, H <- Hs ],
%% Configurations = [ enoise_protocol:to_name(xk, dh25519, 'ChaChaPoly', blake2b) ],
%% Configurations = [ znoise_protocol:to_name(xk, dh25519, 'ChaChaPoly', blake2b) ],
{Configurations, SrvKeyPair, CliKeyPair}.
noise_test(Conf, SKP, CKP) ->
#{econn := EConn, echo_srv := EchoSrv} = noise_test_run(Conf, SKP, CKP),
enoise:close(EConn),
enoise_utils:echo_srv_stop(EchoSrv),
znoise:close(EConn),
znoise_utils:echo_srv_stop(EchoSrv),
ok.
noise_test_run(Conf, SKP, CKP) ->
@@ -145,25 +145,25 @@ proxy_exec(P, F) when is_function(F, 0) ->
end.
noise_test_run_(Conf, SKP, CKP) ->
Protocol = enoise_protocol:from_name(Conf),
Protocol = znoise_protocol:from_name(Conf),
Port = 4556,
SrvOpts = [{echos, 2}, {cpub, enoise_keypair:pubkey(CKP)}],
EchoSrv = enoise_utils:echo_srv_start(Port, Protocol, SKP, SrvOpts),
SrvOpts = [{echos, 2}, {cpub, znoise_keypair:pubkey(CKP)}],
EchoSrv = znoise_utils:echo_srv_start(Port, Protocol, SKP, SrvOpts),
{ok, TcpSock} = gen_tcp:connect("localhost", Port, [{active, once}, binary, {reuseaddr, true}], 100),
Opts = [{noise, Protocol}, {s, CKP}] ++ [{rs, enoise_keypair:pubkey(SKP)} || enoise_utils:need_rs(initiator, Conf) ],
{ok, EConn, _} = enoise:connect(TcpSock, Opts),
Opts = [{noise, Protocol}, {s, CKP}] ++ [{rs, znoise_keypair:pubkey(SKP)} || znoise_utils:need_rs(initiator, Conf) ],
{ok, EConn, _} = znoise:connect(TcpSock, Opts),
ok = enoise:send(EConn, <<"Hello World!">>),
ok = znoise:send(EConn, <<"Hello World!">>),
receive
{noise, _, <<"Hello World!">>} -> ok
after 100 -> error(timeout) end,
enoise:set_active(EConn, once),
znoise:set_active(EConn, once),
ok = enoise:send(EConn, <<"Goodbye!">>),
ok = znoise:send(EConn, <<"Goodbye!">>),
receive
{noise, _, <<"Goodbye!">>} -> ok
after 100 -> error(timeout) end,
@@ -172,7 +172,7 @@ noise_test_run_(Conf, SKP, CKP) ->
, echo_srv => EchoSrv }.
noise_monitor_test(Conf, SKP, CKP) ->
#{ econn := {enoise, EConnPid}
#{ econn := {znoise, EConnPid}
, proxy := Proxy
, tcp_sock := _TcpSock } = noise_test_run(Conf, SKP, CKP),
try proxy_exec(Proxy, fun() -> exit(normal) end)
@@ -185,7 +185,7 @@ noise_monitor_test(Conf, SKP, CKP) ->
%% Talks to local echo-server (noise-c)
%% client_test() ->
%% TestProtocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
%% TestProtocol = znoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
%% ClientPrivKey = <<64,168,119,119,151,194,94,141,86,245,144,220,78,53,243,231,168,216,66,199,49,148,202,117,98,40,61,109,170,37,133,122>>,
%% ClientPubKey = <<115,39,86,77,44,85,192,176,202,11,4,6,194,144,127,123, 34,67,62,180,190,232,251,5,216,168,192,190,134,65,13,64>>,
%% ServerPubKey = <<112,91,141,253,183,66,217,102,211,40,13,249,238,51,77,114,163,159,32,1,162,219,76,106,89,164,34,71,149,2,103,59>>,
@@ -194,29 +194,29 @@ noise_monitor_test(Conf, SKP, CKP) ->
%% gen_tcp:send(TcpSock, <<0,8,0,0,3>>), %% "Noise_XK_25519_ChaChaPoly_Blake2b"
%% Opts = [ {noise, TestProtocol}
%% , {s, enoise_keypair:new(dh25519, ClientPrivKey, ClientPubKey)}
%% , {rs, enoise_keypair:new(dh25519, ServerPubKey)}
%% , {s, znoise_keypair:new(dh25519, ClientPrivKey, ClientPubKey)}
%% , {rs, znoise_keypair:new(dh25519, ServerPubKey)}
%% , {prologue, <<0,8,0,0,3>>}],
%% {ok, EConn} = enoise:connect(TcpSock, Opts),
%% ok = enoise:send(EConn, <<"ok\n">>),
%% {ok, EConn} = znoise:connect(TcpSock, Opts),
%% ok = znoise:send(EConn, <<"ok\n">>),
%% receive
%% {noise, EConn, <<"ok\n">>} -> ok
%% after 1000 -> error(timeout) end,
%% %% {ok, <<"ok\n">>} = enoise:recv(EConn, 3, 1000),
%% enoise:close(EConn).
%% %% {ok, <<"ok\n">>} = znoise:recv(EConn, 3, 1000),
%% znoise:close(EConn).
%% Expects a call-in from a local echo-client (noise-c)
%% server_test_() ->
%% {timeout, 20, fun() ->
%% TestProtocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_Blake2b"),
%% TestProtocol = znoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_Blake2b"),
%% ServerPrivKey = <<200,81,196,192,228,196,182,200,181,83,169,255,242,54,99,113,8,49,129,92,225,220,99,50,93,96,253,250,116,196,137,103>>,
%% ServerPubKey = <<112,91,141,253,183,66,217,102,211,40,13,249,238,51,77,114,163,159,32,1,162,219,76,106,89,164,34,71,149,2,103,59>>,
%% Opts = [ {noise, TestProtocol}
%% , {s, enoise_keypair:new(dh25519, ServerPrivKey, ServerPubKey)}
%% , {s, znoise_keypair:new(dh25519, ServerPrivKey, ServerPubKey)}
%% , {prologue, <<0,8,0,0,3>>}],
%% {ok, LSock} = gen_tcp:listen(7891, [{reuseaddr, true}, binary]),
@@ -226,12 +226,12 @@ noise_monitor_test(Conf, SKP, CKP) ->
%% receive {tcp, TcpSock, <<0,8,0,0,3>>} -> ok
%% after 1000 -> error(timeout) end,
%% {ok, EConn} = enoise:accept(TcpSock, Opts),
%% {ok, EConn} = znoise:accept(TcpSock, Opts),
%% {EConn1, Msg} = enoise:recv(EConn),
%% EConn2 = enoise:send(EConn1, Msg),
%% {EConn1, Msg} = znoise:recv(EConn),
%% EConn2 = znoise:send(EConn1, Msg),
%% enoise:close(EConn2)
%% znoise:close(EConn2)
%% end}.
@@ -2,7 +2,7 @@
%%% @copyright (C) 2018, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(enoise_utils).
-module(znoise_utils).
-compile([export_all, nowarn_export_all]).
@@ -25,7 +25,7 @@ echo_srv(Port, Protocol, SKP, SrvOpts) ->
AcceptRes =
try
enoise:accept(TcpSock, Opts)
znoise:accept(TcpSock, Opts)
catch _:R:S -> gen_tcp:close(TcpSock), {error, {R, S}} end,
gen_tcp:close(LSock),
@@ -46,7 +46,7 @@ echo_srv_loop(EConn, SrvOpts) ->
end;
active ->
fun() ->
{ok, Msg} = enoise:recv(EConn, 0, 100),
{ok, Msg} = znoise:recv(EConn, 0, 100),
Msg
end
end,
@@ -56,14 +56,14 @@ echo_srv_loop(EConn, SrvOpts) ->
try
[ begin
Msg = Recv(),
ok = enoise:send(EConn, Msg)
ok = znoise:send(EConn, Msg)
end || _ <- lists:seq(1, Echos) ],
ok
catch _:R -> {error, R} end,
srv_reply(Res, SrvOpts),
enoise:close(EConn),
znoise:close(EConn),
Res.
@@ -74,8 +74,8 @@ srv_reply(Reply, SrvOpts) ->
end.
need_rs(Role, Conf) when is_binary(Conf) ->
need_rs(Role, enoise_protocol:from_name(Conf));
need_rs(Role, znoise_protocol:from_name(Conf));
need_rs(Role, Protocol) ->
PreMsgs = enoise_protocol:pre_msgs(Role, Protocol),
PreMsgs = znoise_protocol:pre_msgs(Role, Protocol),
lists:member({in, [s]}, PreMsgs).
+17
View File
@@ -0,0 +1,17 @@
{name,"zNoise"}.
{type,lib}.
{modules,[]}.
{prefix,none}.
{desc,"An Erlang implementation of the Noise protocol, adapted for zx"}.
{author,"Craig Everett"}.
{package_id,{"otpr","znoise",{0,1,0}}}.
{deps,[]}.
{key_name,none}.
{a_email,"craigeverett@qpq.swiss"}.
{c_email,"craigeverett@qpq.swiss"}.
{copyright,"Craig Everett"}.
{file_exts,[]}.
{license,"ISC"}.
{repo_url,"https://git.qpq.swiss/zxq9/zNoise"}.
{tags,[]}.
{ws_url,"https://git.qpq.swiss/zxq9/zNoise"}.