1
0
forked from QPQ-AG/enoise

WIP: Reorientation

This commit is contained in:
2026-06-12 11:02:47 +09:00
parent a5da9d08f5
commit d7c8f1ec29
25 changed files with 361 additions and 364 deletions
-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"}]}
]}.
+42 -38
View File
@@ -2,14 +2,18 @@
%%% @copyright 2018, Aeternity Anstalt
%%%
%%% @doc
%%% Interface to the [Noise protocol](https://noiseprotocol.org)
%%% Interface to the Noise protocol: https://noiseprotocol.org
%%%
%%% 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
%%% and after "upgrading" a `gen_tcp'-socket into a `znoise'-socket it has a
%%% similar API as `gen_tcp'.
%%% @end
-module(enoise).
-module(znoise).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
%% Main function with generic Noise handshake
-export([handshake/2, handshake/3, step_handshake/2]).
@@ -22,10 +26,10 @@
send/2,
set_active/2]).
-record(enoise, {pid}).
-record(znoise, {pid}).
-type key() :: binary().
-type keypair() :: enoise_keypair:keypair().
-type keypair() :: znoise_keypair:keypair().
-type options() :: [option()].
%% A list of Noise options is a proplist, it *must* contain a value `noise'
@@ -42,7 +46,7 @@
| {prologue, binary()} %% Optional
| {timeout, integer() | infinity}. %% Optional
-type protocol_option() :: enoise_protocol:protocol()
-type protocol_option() :: znoise_protocol:protocol()
| string()
| binary().
%% Either an instantiated Noise protocol configuration or the name of a Noise
@@ -64,12 +68,12 @@
%% Noise communication state - used to parameterize a handshake. Consists of a
%% send function, one receive function, and an internal state.
-type split_state() :: enoise_hs_state:noise_split_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() :: #enoise{}.
-opaque socket() :: #znoise{}.
%% An abstract Noise socket - holds a reference to a socket that has completed
%% a Noise handshake.
@@ -81,8 +85,8 @@
-spec handshake(Options, Role) -> Outcome
when Options :: options(),
Role :: enoise_hs_state:noise_role(),
Outcome :: {ok, enoise_hs_state:state()}
Role :: znoise_hs_state:noise_role(),
Outcome :: {ok, znoise_hs_state:state()}
| {error, term()}.
%% @doc
%% Start an interactive handshake
@@ -93,7 +97,7 @@ handshake(Options, Role) ->
-spec handshake(Options, Role, ComState) -> Outcome
when Options :: options(),
Role :: enoise_hs_state:noise_role(),
Role :: znoise_hs_state:noise_role(),
ComState :: com_state(),
Outcome :: {ok, split_state(), com_state()}
| {error, term()}.
@@ -111,11 +115,11 @@ handshake(Options, Role, ComState) ->
-spec step_handshake(HState, Data) -> Next
when HState :: enoise_hs_state:state(),
when HState :: znoise_hs_state:state(),
Data :: {rcvd, binary()}
| {send, binary()},
Next :: {ok, send, binary(), enoise_hs_state:state()}
| {ok, rcvd, binary(), enoise_hs_state:state()}
Next :: {ok, send, binary(), znoise_hs_state:state()}
| {ok, rcvd, binary(), znoise_hs_state:state()}
| {ok, done, split_state()}
| {error, term()}.
%% @doc
@@ -128,7 +132,7 @@ step_handshake(HState, Data) ->
-spec connect(TcpSock, Options) -> Outcome
when TcpSock :: gen_tcp:socket(),
Options :: options(),
Outcome :: {ok, socket(), enoise_hs_state:state()}
Outcome :: {ok, socket(), znoise_hs_state:state()}
| {error, term()}.
%% @doc
%% Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
@@ -146,7 +150,7 @@ connect(TcpSock, Options) ->
-spec accept(TcpSock, Options) -> Outcome
when TcpSock :: gen_tcp:socket(),
Options :: options(),
Outcome :: {ok, socket(), enoise_hs_state:state()}
Outcome :: {ok, socket(), znoise_hs_state:state()}
| {error, term()}.
%% @doc
%% Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
@@ -168,8 +172,8 @@ accept(TcpSock, Options) ->
%% @doc
%% Writes `Data' to `Socket'
send(#enoise{ pid = Pid }, Data) ->
enoise_connection:send(Pid, Data).
send(#znoise{ pid = Pid }, Data) ->
znoise_tcp:send(Pid, Data).
-spec close(NoiseSock) -> Outcome
@@ -178,8 +182,8 @@ send(#enoise{ pid = Pid }, Data) ->
%% @doc
%% Closes a Noise connection.
close(#enoise{ pid = Pid }) ->
enoise_connection:close(Pid).
close(#znoise{ pid = Pid }) ->
znoise_tcp:close(Pid).
-spec controlling_process(Socket, Pid) -> Outcome
@@ -191,8 +195,8 @@ close(#enoise{ pid = Pid }) ->
%% process is the owner of an Noise socket, and receives all messages from the
%% socket.
controlling_process(#enoise{ pid = Pid }, NewPid) ->
enoise_connection:controlling_process(Pid, NewPid).
controlling_process(#znoise{ pid = Pid }, NewPid) ->
znoise_tcp:controlling_process(Pid, NewPid).
-spec set_active(Socket, Mode) -> Outcome
@@ -203,19 +207,19 @@ controlling_process(#enoise{ pid = Pid }, NewPid) ->
%% Set the active option `true | once'. Note that `N' and `false' are
%% not valid options for a Noise socket.
set_active(#enoise{ pid = Pid }, ActiveMode) ->
enoise_connection:set_active(Pid, ActiveMode).
set_active(#znoise{ pid = Pid }, ActiveMode) ->
znoise_tcp:set_active(Pid, ActiveMode).
%%% Internal functions
do_handshake(HState, ComState, Timeout) ->
case enoise_hs_state:next_message(HState) of
case znoise_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
case znoise_hs_state:read_message(HState, Data) of
{ok, HState1, _Msg} ->
do_handshake(HState1, ComState1, Timeout);
Err = {error, _} ->
@@ -225,7 +229,7 @@ do_handshake(HState, ComState, Timeout) ->
Err
end;
out ->
{ok, HState1, Msg} = enoise_hs_state:write_message(HState, <<>>),
{ok, HState1, Msg} = znoise_hs_state:write_message(HState, <<>>),
case hs_send_msg(ComState, Msg) of
{ok, ComState1} ->
do_handshake(HState1, ComState1, Timeout);
@@ -233,7 +237,7 @@ do_handshake(HState, ComState, Timeout) ->
Err
end;
done ->
{ok, Res} = enoise_hs_state:finalize(HState),
{ok, Res} = znoise_hs_state:finalize(HState),
{ok, Res, ComState}
end.
@@ -250,19 +254,19 @@ hs_send_msg(CS = #{ send_msg := Send, state := S }, Data) ->
end.
do_step_handshake(HState, Data) ->
case {enoise_hs_state:next_message(HState), Data} of
case {znoise_hs_state:next_message(HState), Data} of
{in, {rcvd, Encrypted}} ->
case enoise_hs_state:read_message(HState, Encrypted) of
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} = enoise_hs_state:write_message(HState, Payload),
{ok, HState1, Msg} = znoise_hs_state:write_message(HState, Payload),
{ok, send, Msg, HState1};
{done, done} ->
{ok, Res} = enoise_hs_state:finalize(HState),
{ok, Res} = znoise_hs_state:finalize(HState),
{ok, done, Res};
{Next, _} ->
{error, {invalid_step, expected, Next, got, Data}}
@@ -289,8 +293,8 @@ do_tcp_handshake(Options, Role, TcpSock, Active) ->
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};
case znoise_tcp:start_link(TcpSock, Rx, Tx, self(), {Active, Buf}) of
{ok, Pid} -> {ok, #znoise{ pid = Pid }, FState};
Error -> Error
end;
Error ->
@@ -303,15 +307,15 @@ create_hstate(Options, Role) ->
Noise = proplists:get_value(noise, Options),
Protocol =
case is_binary(Noise) orelse is_list(Noise) of
true -> enoise_protocol:from_name(X);
true -> znoise_protocol:from_name(X);
false -> Noise
end,
DH = enoise_protocol:dh(Protocol),
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)),
enoise_hs_state:init(Protocol, Role, Prologue, {S, E, RS, RE}).
znoise_hs_state:init(Protocol, Role, Prologue, {S, E, RS, RE}).
check_gen_tcp(TcpSock) ->
@@ -366,4 +370,4 @@ gen_tcp_rcv_msg({TcpSock, Active, Buf}, Timeout) ->
remote_keypair(_DH, undefined) ->
undefined;
remote_keypair(DH, RemotePub) when is_binary(RemotePub) ->
enoise_keypair:new(DH, RemotePub).
znoise_keypair:new(DH, RemotePub).
@@ -1,12 +1,15 @@
%%% ------------------------------------------------------------------
%%% @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>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([ cipher/1
, decrypt_with_ad/3
@@ -19,7 +22,7 @@
, set_nonce/2
]).
-include("enoise.hrl").
-include("znoise.hrl").
-type noise_cipher() :: 'ChaChaPoly' | 'AESGCM'.
-type nonce() :: non_neg_integer().
@@ -54,7 +57,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 +65,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 +76,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 }) ->
+21 -18
View File
@@ -1,14 +1,17 @@
%%% ------------------------------------------------------------------
%%% @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>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-include("enoise.hrl").
-include("znoise.hrl").
-export([ decrypt/5
, dh/3
@@ -24,14 +27,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 +44,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 +53,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 +62,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 +70,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 +93,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 +116,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.
@@ -5,7 +5,11 @@
%%% 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>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([finalize/1,
init/4,
@@ -14,61 +18,61 @@
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(),
-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 }};
@@ -116,38 +120,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
@@ -168,27 +172,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,15 @@
%%% ------------------------------------------------------------------
%%% @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>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([ key_type/1
, new/1
@@ -1,12 +1,15 @@
%%% ------------------------------------------------------------------
%%% @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>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([ cipher/1
, dh/1
@@ -23,28 +26,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 +83,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,14 @@
%%% ------------------------------------------------------------------
%%% @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>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([ cipher_state/1
, ck/1
@@ -21,11 +23,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 +35,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.
@@ -8,7 +8,11 @@
%%% into Noise packets, so we need some buffering.
%%% @end
-module(enoise_connection).
-module(znoise_tcp).
-vsn("0.1.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>").
-license("ISC").
-export([controlling_process/2,
close/1,
@@ -20,7 +24,7 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(enoise, {pid}).
-record(znoise, {pid}).
-record(s,
{rx = ,
@@ -123,9 +127,9 @@ handle_info({tcp, TS, Data}, State = #s{tcp_sock = TS, owner = O}) ->
NewState = handle_msgs(NextState#s{msgbuf = Buf ++ Msgs}),
set_active(NewState),
{noreply, NewState}
catch error:{enoise_error, _} ->
catch error:{znoise_error, _} ->
%% We are not likely to recover, but leave the decision to upstream
O ! {enoise_error, TS, decrypt_error},
O ! {znoise_error, TS, decrypt_error},
{noreply, State}
end;
handle_info({tcp_closed, TS}, State = #s{tcp_sock = TS, owner = O}) ->
@@ -180,12 +184,12 @@ handle_data(S = #state{ rawbuf = Buf, rx = Rx }, Data) ->
{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
case znoise_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})
error({znoise_error, decrypt_input_failed})
end;
EmptyOrSingleByte ->
{S#state{ rawbuf = EmptyOrSingleByte }, []}
@@ -194,19 +198,19 @@ handle_data(S = #state{ rawbuf = Buf, rx = Rx }, Data) ->
handle_msgs(S = #state{ msgbuf = [] }) ->
S;
handle_msgs(S = #state{ msgbuf = Msgs, active = true, owner = Owner }) ->
[ Owner ! {noise, #enoise{ pid = self() }, Msg} || Msg <- Msgs ],
[ Owner ! {noise, #znoise{ 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},
Owner ! {noise, #znoise{ 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),
{ok, Tx1, Msg} = znoise_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}