src/jwa/jose_jwa_x448.erl

%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*-
%% vim: ts=4 sw=4 ft=erlang noet
%%%-------------------------------------------------------------------
%%% @author Andrew Bennett <potatosaladx@gmail.com>
%%% @copyright 2014-2022, Andrew Bennett
%%% @doc Elliptic Curves for Security - X448
%%% See https://tools.ietf.org/html/rfc7748
%%% @end
%%% Created :  07 Jan 2016 by Andrew Bennett <potatosaladx@gmail.com>
%%%-------------------------------------------------------------------
-module(jose_jwa_x448).

%% API
-export([coordinate_to_edwards448_4isogeny/1]).
-export([vrecover/1]).
-export([xrecover/1]).
-export([curve448/2]).
-export([clamp_scalar/1]).
-export([decode_scalar/1]).
-export([montgomery_add/3]).
-export([montgomery_double/1]).
-export([scalarmult/2]).
-export([scalarmult_base/1]).
-export([x448/2]).
-export([x448_base/1]).
-export([keypair/0]).
-export([keypair/1]).
-export([sk_to_pk/1]).

%% Macros
-define(math, jose_jwa_math).
-define(inv(Z), ?math:expmod(Z, ?p - 2, ?p)). % $= z^{-1} \mod p$, for z != 0
-define(d, 611975850744529176160423220965553317543219696871016626328968936415087860042636474891785599283666020414768678979989378147065462815545017).
% 4.2. Curve448 - https://tools.ietf.org/html/rfc7748#section-4.2
-define(p, 726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439). % ?math:intpow(2, 448) - ?math:intpow(2, 224) - 1
-define(A, 156326).
-define(A2sqrt, 528950257000142451010969798134146496096806208096258258133290419984524923934728256972792120570883515182233459997657945594599653183173011). % ?math:expmod((?A - 2), (?p + 1) div 4, ?p)
-define(order, 181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779). % ?math:intpow(2, 446) - 16#8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d
-define(cofactor, 4).
-define(u, 5).
-define(v, 355293926785568175264127502063783334808976399387714271831880898435169088786967410002932673765864550910142774147268105838985595290606362).
-define(b, 448).
-define(a24, 39081). % (?A - 2) div 4
-define(scalarbytes, 56). % (?b + 7) div 8
-define(coordinatebytes, 56). % (?b + 7) div 8

-define(publickeybytes, 56). % ?coordinatebytes
-define(secretkeybytes, 56). % ?scalarbytes

%%====================================================================
%% API
%%====================================================================

coordinate_to_edwards448_4isogeny(<< U:?b/unsigned-little-integer-unit:1 >>) ->
	V = vrecover(U),
	% -(u^5 - 2*u^3 - 4*u*v^2 + u)/(u^5 - 2*u^2*v^2 - 2*u^3 - 2*v^2 + u)
	U2 = ?math:expmod(U, 2, ?p),
	U3 = ?math:expmod(U, 3, ?p),
	U5 = ?math:mod(U2*U3, ?p),
	V2 = ?math:expmod(V, 2, ?p),
	YN = ?math:mod(-(U5 - (2*U3) - (4*U*V2) + U), ?p),
	YD = ?inv((U5 - (2*U2*V2) - (2*U3) - (2*V2) + U)),
	Y = ?math:mod(YN*YD, ?p),
	% 4*v*(u^2 - 1)/(u^4 - 2*u^2 + 4*v^2 + 1)
	U4 = ?math:mod(U2*U2, ?p),
	XN = ?math:mod((4*V*(U2 - 1)), ?p),
	XD = ?inv((U4 - (2*U2) + (4*V2) + 1)),
	X = ?math:mod(XN*XD, ?p),
	jose_jwa_ed448:encode_point({X, Y, 1}).

vrecover(U) ->
	Y = ?math:mod(((1 + U) * ?inv(1 - U)), ?p),
	X = xrecover(Y),
	UX = (U * ?inv(X)) rem ?p,
	V = (?A2sqrt * UX) rem ?p,
	case V rem 2 of
		0 ->
			V;
		_ ->
			?p - V
	end.

xrecover(Y) ->
	YY = Y * Y,
	U = (YY - 1) rem ?p,
	V = ((?d * YY) - 1) rem ?p,
	A = (U * ?inv(V)) rem ?p,
	X = ?math:expmod(A, (?p + 1) div 4, ?p),
	case ((X * X) - A) rem ?p =:= 0 of
		true -> % x^2 = a (mod p).  Then x is a square root.
			case X rem 2 of
				0 ->
					X;
				_ ->
					?p - X
			end;
		false -> % a is not a square modulo p.
			erlang:error(badarg)
	end.

curve448(N, Base) ->
	One = {Base, 1},
	Two = montgomery_double(One),
	% f(m) evaluates to a tuple containing the mth multiple and the
	% (m+1)th multiple of base.
	F = fun
		F(1) ->
			{One, Two};
		F(M) when (M band 1) =:= 1 ->
			{Pm, Pm1} = F(M div 2),
			{montgomery_add(Pm, Pm1, One), montgomery_double(Pm1)};
		F(M) ->
			{Pm, Pm1} = F(M div 2),
			{montgomery_double(Pm), montgomery_add(Pm, Pm1, One)}
	end,
	{{X, Z}, _} = F(N),
	(X * ?inv(Z)) rem ?p.

% 5. The X25519 and X448 functions - https://tools.ietf.org/html/rfc7748#section-5

clamp_scalar(K0) when is_integer(K0) ->
	K1 = K0 band (bnot 3),
	K2 = K1 bor (128 bsl (?b - 8)),
	K2.

decode_scalar(<< K0:8/integer, KBody:(?b - 16)/bitstring, K55:8/integer >>) ->
	<< K:?b/unsigned-little-integer-unit:1 >> = << (K0 band 252):8/integer, KBody/bitstring, (K55 bor 128):8/integer >>,
	K.

montgomery_add({Xn, Zn}, {Xm, Zm}, {Xd, Zd}) ->
	Z0 = (Xm * Xn) - (Zm * Zn),
	Z1 = (Xm * Zn) - (Zm * Xn),
	X = 4 * Z0 * Z0 * Zd,
	Z = 4 * Z1 * Z1 * Xd,
	{X rem ?p, Z rem ?p}.

montgomery_double({Xn, Zn}) ->
	Xn2 = Xn * Xn,
	Zn2 = Zn * Zn,
	X = (Xn2 - Zn2),
	X2 = X * X,
	Z = 4 * Xn * Zn * (Xn2 + (?A * Xn * Zn) + Zn2),
	{X2 rem ?p, Z rem ?p}.

scalarmult(<< Kb:?b/unsigned-little-integer-unit:1 >>, << U:?b/unsigned-little-integer-unit:1 >>) ->
	K = clamp_scalar(Kb),
	R = curve448(K, U),
	<< R:?b/unsigned-little-integer-unit:1 >>.

scalarmult_base(<< Kb:?b/unsigned-little-integer-unit:1 >>) ->
	K = clamp_scalar(Kb),
	R = curve448(K, ?u),
	<< R:?b/unsigned-little-integer-unit:1 >>.

x448(SK = << _:?secretkeybytes/binary >>, PK = << _:?publickeybytes/binary >>) ->
	scalarmult(SK, PK).

x448_base(SK = << _:?secretkeybytes/binary >>) ->
	scalarmult_base(SK).

keypair() ->
	keypair(crypto:strong_rand_bytes(?secretkeybytes)).

keypair(SK = << _:?secretkeybytes/binary >>) ->
	PK = sk_to_pk(SK),
	{PK, SK}.

sk_to_pk(SK = << _:?secretkeybytes/binary >>) ->
	x448_base(SK).