Current section
Files
Jump to
Current section
Files
src/ywt@sign_key.erl
-module(ywt@sign_key).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ywt/sign_key.gleam").
-export([hs256/1, hs384/1, hs512/1, with_random_id/1, algorithm/1, decoder/0, parse_jwk/1, id/1, match/5, to_jwk/1]).
-export_type([sign_key/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" <style>\n"
" .goto-top {\n"
" display: block;\n"
" font-size: 0.85em;\n"
" text-align: right;\n"
" }\n"
" </style>\n"
" <script>\n"
" (callback => document.readyState !== 'loading' ? callback() : document.addEventListener('DOMContentLoaded', callback, { once: true }))(() => {\n"
" const goToTop = document.createElement('a')\n"
" goToTop.classList.add('goto-top')\n"
" goToTop.setAttribute('href', '#')\n"
" goToTop.textContent = 'Back to top ↑'\n"
" for (const member of document.querySelectorAll('.member')) {\n"
" member.insertBefore(goToTop.cloneNode(true), null)\n"
" }\n"
" })\n"
" </script>\n"
).
-opaque sign_key() :: {sign_ecdsa,
gleam@option:option(binary()),
ywt@internal@core:named_curve(),
ywt@internal@core:digest_type(),
bitstring(),
bitstring()} |
{sign_rsa_simple,
gleam@option:option(binary()),
ywt@internal@core:digest_type(),
bigi:big_int(),
bigi:big_int(),
bigi:big_int(),
ywt@internal@core:padding()} |
{sign_rsa_full,
gleam@option:option(binary()),
ywt@internal@core:digest_type(),
bigi:big_int(),
bigi:big_int(),
bigi:big_int(),
bigi:big_int(),
bigi:big_int(),
bigi:big_int(),
bigi:big_int(),
bigi:big_int(),
list({bigi:big_int(), bigi:big_int(), bigi:big_int()}),
ywt@internal@core:padding()} |
{sign_hmac,
gleam@option:option(binary()),
ywt@internal@core:digest_type(),
bitstring()}.
-file("src/ywt/sign_key.gleam", 107).
-spec hmac_key(
gleam@option:option(binary()),
ywt@internal@core:digest_type(),
bitstring()
) -> {ok, sign_key()} | {error, nil}.
hmac_key(Id, Digest_type, Secret) ->
Minimum_bits = case Digest_type of
sha256 ->
256;
sha384 ->
384;
sha512 ->
512
end,
case erlang:bit_size(Secret) >= Minimum_bits of
true ->
{ok, {sign_hmac, Id, Digest_type, Secret}};
false ->
{error, nil}
end.
-file("src/ywt/sign_key.gleam", 75).
?DOC(
" Creates an HMAC-SHA256 signing key.\n"
"\n"
" The secret must be at least 256 bits. HMAC keys are symmetric, so every\n"
" verifier that receives this key can also sign tokens. The length check does\n"
" not prove the bytes are random; use `ywt.generate_key` or a CSPRNG.\n"
"\n"
" ```gleam\n"
" let assert Ok(key) = sign_key.hs256(secret_32_bytes)\n"
" ```\n"
).
-spec hs256(bitstring()) -> {ok, sign_key()} | {error, nil}.
hs256(Secret) ->
hmac_key(none, sha256, Secret).
-file("src/ywt/sign_key.gleam", 90).
?DOC(
" Creates an HMAC-SHA384 signing key.\n"
"\n"
" The secret must be at least 384 bits. HMAC keys are symmetric, so every\n"
" verifier that receives this key can also sign tokens. The length check does\n"
" not prove the bytes are random; use `ywt.generate_key` or a CSPRNG.\n"
"\n"
" ```gleam\n"
" let assert Ok(key) = sign_key.hs384(secret_48_bytes)\n"
" ```\n"
).
-spec hs384(bitstring()) -> {ok, sign_key()} | {error, nil}.
hs384(Secret) ->
hmac_key(none, sha384, Secret).
-file("src/ywt/sign_key.gleam", 103).
?DOC(
" Creates an HMAC-SHA512 signing key.\n"
"\n"
" The secret must be at least 512 bits. HMAC keys are symmetric, so every\n"
" verifier that receives this key can also sign tokens. The length check does\n"
" not prove the bytes are random; use `ywt.generate_key` or a CSPRNG.\n"
"\n"
" ```gleam\n"
" let assert Ok(key) = sign_key.hs512(secret_64_bytes)\n"
" ```\n"
).
-spec hs512(bitstring()) -> {ok, sign_key()} | {error, nil}.
hs512(Secret) ->
hmac_key(none, sha512, Secret).
-file("src/ywt/sign_key.gleam", 132).
?DOC(
" Assigns a random key id to a signing key.\n"
"\n"
" The id is written to JWT headers as `kid`, letting verifiers choose the right\n"
" key during rotation.\n"
"\n"
" ```gleam\n"
" let key = sign_key.with_random_id(key)\n"
" ```\n"
).
-spec with_random_id(sign_key()) -> sign_key().
with_random_id(Key) ->
Id = {some, ywt_core_ffi:random_id()},
case Key of
{sign_ecdsa, _, _, _, _, _} ->
{sign_ecdsa,
Id,
erlang:element(3, Key),
erlang:element(4, Key),
erlang:element(5, Key),
erlang:element(6, Key)};
{sign_hmac, _, _, _} ->
{sign_hmac, Id, erlang:element(3, Key), erlang:element(4, Key)};
{sign_rsa_full, _, _, _, _, _, _, _, _, _, _, _, _} ->
{sign_rsa_full,
Id,
erlang:element(3, Key),
erlang:element(4, Key),
erlang:element(5, Key),
erlang:element(6, Key),
erlang:element(7, Key),
erlang:element(8, Key),
erlang:element(9, Key),
erlang:element(10, Key),
erlang:element(11, Key),
erlang:element(12, Key),
erlang:element(13, Key)};
{sign_rsa_simple, _, _, _, _, _, _} ->
{sign_rsa_simple,
Id,
erlang:element(3, Key),
erlang:element(4, Key),
erlang:element(5, Key),
erlang:element(6, Key),
erlang:element(7, Key)}
end.
-file("src/ywt/sign_key.gleam", 403).
-spec other_primes_info_decoder() -> gleam@dynamic@decode:decoder({bigi:big_int(),
bigi:big_int(),
bigi:big_int()}).
other_primes_info_decoder() ->
Field_decoder = ywt@internal@core:int_decoder(),
gleam@dynamic@decode:field(
<<"r"/utf8>>,
Field_decoder,
fun(Factor) ->
gleam@dynamic@decode:field(
<<"d"/utf8>>,
Field_decoder,
fun(Exponent) ->
gleam@dynamic@decode:field(
<<"t"/utf8>>,
Field_decoder,
fun(Coefficient) ->
gleam@dynamic@decode:success(
{Factor, Exponent, Coefficient}
)
end
)
end
)
end
).
-file("src/ywt/sign_key.gleam", 296).
-spec rsa_private_parameters_decoder(
gleam@option:option(binary()),
ywt@internal@core:digest_type(),
ywt@internal@core:padding(),
bigi:big_int(),
bigi:big_int(),
bigi:big_int()
) -> gleam@dynamic@decode:decoder(sign_key()).
rsa_private_parameters_decoder(
Id,
Digest_type,
Padding,
Public_exponent,
Modulus,
Private_exponent
) ->
Field_decoder = ywt@internal@core:int_decoder(),
Optional_int = gleam@dynamic@decode:map(
Field_decoder,
fun(Field@0) -> {some, Field@0} end
),
Simple_key = {sign_rsa_simple,
Id,
Digest_type,
Public_exponent,
Modulus,
Private_exponent,
Padding},
gleam@dynamic@decode:optional_field(
<<"p"/utf8>>,
none,
Optional_int,
fun(First_prime_factor) ->
gleam@dynamic@decode:optional_field(
<<"q"/utf8>>,
none,
Optional_int,
fun(Second_prime_factor) ->
gleam@dynamic@decode:optional_field(
<<"dp"/utf8>>,
none,
Optional_int,
fun(First_factor_crt_exponent) ->
gleam@dynamic@decode:optional_field(
<<"dq"/utf8>>,
none,
Optional_int,
fun(Second_factor_crt_exponent) ->
gleam@dynamic@decode:optional_field(
<<"qi"/utf8>>,
none,
Optional_int,
fun(First_crt_coefficient) ->
gleam@dynamic@decode:optional_field(
<<"oth"/utf8>>,
[],
gleam@dynamic@decode:list(
other_primes_info_decoder()
),
fun(Other_primes_info) ->
case {First_prime_factor,
Second_prime_factor,
First_factor_crt_exponent,
Second_factor_crt_exponent,
First_crt_coefficient,
Other_primes_info} of
{none,
none,
none,
none,
none,
[]} ->
gleam@dynamic@decode:success(
Simple_key
);
{{some,
First_prime_factor@1},
{some,
Second_prime_factor@1},
{some,
First_factor_crt_exponent@1},
{some,
Second_factor_crt_exponent@1},
{some,
First_crt_coefficient@1},
Other_primes_info@1} ->
gleam@dynamic@decode:success(
{sign_rsa_full,
Id,
Digest_type,
Public_exponent,
Modulus,
Private_exponent,
First_prime_factor@1,
Second_prime_factor@1,
First_factor_crt_exponent@1,
Second_factor_crt_exponent@1,
First_crt_coefficient@1,
Other_primes_info@1,
Padding}
);
{_, _, _, _, _, _} ->
gleam@dynamic@decode:failure(
Simple_key,
<<"RSA CRT parameters"/utf8>>
)
end
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/ywt/sign_key.gleam", 486).
?DOC(false).
-spec algorithm(sign_key()) -> ywt@algorithm:algorithm().
algorithm(Key) ->
case Key of
{sign_ecdsa, _, _, sha256, _, _} ->
es256;
{sign_ecdsa, _, _, sha384, _, _} ->
es384;
{sign_ecdsa, _, _, sha512, _, _} ->
es512;
{sign_hmac, _, sha256, _} ->
hs256;
{sign_hmac, _, sha384, _} ->
hs384;
{sign_hmac, _, sha512, _} ->
hs512;
{sign_rsa_simple, _, sha256, _, _, _, rsa_pkcs1_padding} ->
rs256;
{sign_rsa_full, _, sha256, _, _, _, _, _, _, _, _, _, rsa_pkcs1_padding} ->
rs256;
{sign_rsa_simple, _, sha384, _, _, _, rsa_pkcs1_padding} ->
rs384;
{sign_rsa_full, _, sha384, _, _, _, _, _, _, _, _, _, rsa_pkcs1_padding} ->
rs384;
{sign_rsa_simple, _, sha512, _, _, _, rsa_pkcs1_padding} ->
rs512;
{sign_rsa_full, _, sha512, _, _, _, _, _, _, _, _, _, rsa_pkcs1_padding} ->
rs512;
{sign_rsa_simple, _, sha256, _, _, _, rsa_pkcs1_pss_padding} ->
ps256;
{sign_rsa_full,
_,
sha256,
_,
_,
_,
_,
_,
_,
_,
_,
_,
rsa_pkcs1_pss_padding} ->
ps256;
{sign_rsa_simple, _, sha384, _, _, _, rsa_pkcs1_pss_padding} ->
ps384;
{sign_rsa_full,
_,
sha384,
_,
_,
_,
_,
_,
_,
_,
_,
_,
rsa_pkcs1_pss_padding} ->
ps384;
{sign_rsa_simple, _, sha512, _, _, _, rsa_pkcs1_pss_padding} ->
ps512;
{sign_rsa_full,
_,
sha512,
_,
_,
_,
_,
_,
_,
_,
_,
_,
rsa_pkcs1_pss_padding} ->
ps512
end.
-file("src/ywt/sign_key.gleam", 249).
-spec rsa_decoder(gleam@option:option(binary())) -> gleam@dynamic@decode:decoder(sign_key()).
rsa_decoder(Id) ->
gleam@dynamic@decode:field(
<<"alg"/utf8>>,
ywt@algorithm:decoder(),
fun(Alg) ->
Field_decoder = ywt@internal@core:int_decoder(),
gleam@dynamic@decode:field(
<<"e"/utf8>>,
Field_decoder,
fun(Public_exponent) ->
gleam@dynamic@decode:field(
<<"n"/utf8>>,
Field_decoder,
fun(Modulus) ->
gleam@dynamic@decode:field(
<<"d"/utf8>>,
Field_decoder,
fun(Private_exponent) ->
Digest_type = ywt@algorithm:digest_type(Alg),
gleam@dynamic@decode:then(
case ywt@algorithm:padding(Alg) of
{ok, Padding} ->
gleam@dynamic@decode:success(
Padding
);
{error, _} ->
gleam@dynamic@decode:failure(
rsa_pkcs1_padding,
<<"alg"/utf8>>
)
end,
fun(Padding@1) ->
gleam@dynamic@decode:then(
case ywt@internal@core:rsa_modulus_is_large_enough(
Modulus
) of
true ->
gleam@dynamic@decode:success(
nil
);
false ->
gleam@dynamic@decode:failure(
nil,
<<"n"/utf8>>
)
end,
fun(_) ->
rsa_private_parameters_decoder(
Id,
Digest_type,
Padding@1,
Public_exponent,
Modulus,
Private_exponent
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/ywt/sign_key.gleam", 198).
-spec ec_decoder(gleam@option:option(binary())) -> gleam@dynamic@decode:decoder(sign_key()).
ec_decoder(Id) ->
gleam@dynamic@decode:field(
<<"crv"/utf8>>,
ywt@internal@core:curve_decoder(),
fun(Crv) ->
Coord_size = ywt@internal@core:named_curve_size(Crv),
gleam@dynamic@decode:field(
<<"x"/utf8>>,
ywt@internal@core:bits_of_length_decoder(Coord_size),
fun(X) ->
gleam@dynamic@decode:field(
<<"y"/utf8>>,
ywt@internal@core:bits_of_length_decoder(Coord_size),
fun(Y) ->
gleam@dynamic@decode:field(
<<"d"/utf8>>,
ywt@internal@core:bits_of_length_decoder(
Coord_size
),
fun(Private_key) ->
Public_key = <<16#4,
X/bitstring,
Y/bitstring>>,
Digest_type = case Crv of
secp256r1 ->
sha256;
secp384r1 ->
sha384;
secp521r1 ->
sha512
end,
Sign_key = {sign_ecdsa,
Id,
Crv,
Digest_type,
Public_key,
Private_key},
Expected_alg = ywt@algorithm:ec_algorithm(
Crv
),
gleam@dynamic@decode:optional_field(
<<"alg"/utf8>>,
Expected_alg,
ywt@algorithm:decoder(),
fun(Alg) -> case Alg =:= Expected_alg of
true ->
gleam@dynamic@decode:success(
Sign_key
);
false ->
gleam@dynamic@decode:failure(
Sign_key,
<<"alg"/utf8>>
)
end end
)
end
)
end
)
end
)
end
).
-file("src/ywt/sign_key.gleam", 183).
-spec oct_decoder(gleam@option:option(binary())) -> gleam@dynamic@decode:decoder(sign_key()).
oct_decoder(Id) ->
gleam@dynamic@decode:field(
<<"alg"/utf8>>,
ywt@algorithm:decoder(),
fun(Alg) ->
gleam@dynamic@decode:field(
<<"k"/utf8>>,
ywt@internal@core:bits_decoder(),
fun(K) ->
Digest_type = ywt@algorithm:digest_type(Alg),
case ywt@algorithm:is_hmac(Alg) of
true ->
case hmac_key(Id, Digest_type, K) of
{ok, Key} ->
gleam@dynamic@decode:success(Key);
{error, _} ->
gleam@dynamic@decode:failure(
{sign_hmac, Id, Digest_type, K},
<<"k"/utf8>>
)
end;
false ->
gleam@dynamic@decode:failure(
{sign_hmac, Id, Digest_type, K},
<<"alg"/utf8>>
)
end
end
)
end
).
-file("src/ywt/sign_key.gleam", 157).
?DOC(
" Decodes a private JWK into a signing key.\n"
"\n"
" Only decode private keys from trusted storage. Unknown JWK fields are ignored;\n"
" `key_ops` and `use` are not enforced. HMAC and RSA signing JWKs must include\n"
" `alg`; EC signing JWKs may omit `alg` when the curve determines it.\n"
" For raw JSON strings, prefer `parse_jwk` so duplicate member handling is\n"
" consistent on all targets.\n"
"\n"
" ```gleam\n"
" json.parse(private_jwk, sign_key.decoder())\n"
" ```\n"
).
-spec decoder() -> gleam@dynamic@decode:decoder(sign_key()).
decoder() ->
gleam@dynamic@decode:optional_field(
<<"kid"/utf8>>,
none,
gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Field@0) -> {some, Field@0} end
),
fun(Id) ->
gleam@dynamic@decode:field(
<<"kty"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Kty) -> case Kty of
<<"oct"/utf8>> ->
oct_decoder(Id);
<<"EC"/utf8>> ->
ec_decoder(Id);
<<"RSA"/utf8>> ->
rsa_decoder(Id);
_ ->
gleam@dynamic@decode:failure(
{sign_hmac, Id, sha256, <<>>},
<<"kty"/utf8>>
)
end end
)
end
).
-file("src/ywt/sign_key.gleam", 179).
?DOC(
" Parses a private JWK into a signing key.\n"
"\n"
" Duplicate JSON object members are handled consistently on all targets: the\n"
" lexically last member value is used, matching JavaScript's `JSON.parse`.\n"
"\n"
" ```gleam\n"
" sign_key.parse_jwk(private_jwk)\n"
" ```\n"
).
-spec parse_jwk(binary()) -> {ok, sign_key()} |
{error, gleam@json:decode_error()}.
parse_jwk(Private_jwk) ->
ywt@internal@jose_json:parse(Private_jwk, decoder()).
-file("src/ywt/sign_key.gleam", 426).
?DOC(
" Returns the key id, if one is set.\n"
"\n"
" ```gleam\n"
" sign_key.id(key)\n"
" ```\n"
).
-spec id(sign_key()) -> {ok, binary()} | {error, nil}.
id(Key) ->
gleam@option:to_result(erlang:element(2, Key), nil).
-file("src/ywt/sign_key.gleam", 433).
?DOC(false).
-spec match(
sign_key(),
fun((gleam@option:option(binary()), ywt@internal@core:named_curve(), ywt@internal@core:digest_type(), bitstring(), bitstring()) -> FBW),
fun((gleam@option:option(binary()), ywt@internal@core:digest_type(), bigi:big_int(), bigi:big_int(), bigi:big_int(), ywt@internal@core:padding()) -> FBW),
fun((gleam@option:option(binary()), ywt@internal@core:digest_type(), bigi:big_int(), bigi:big_int(), bigi:big_int(), bigi:big_int(), bigi:big_int(), bigi:big_int(), bigi:big_int(), bigi:big_int(), list({bigi:big_int(),
bigi:big_int(),
bigi:big_int()}), ywt@internal@core:padding()) -> FBW),
fun((gleam@option:option(binary()), ywt@internal@core:digest_type(), bitstring()) -> FBW)
) -> FBW.
match(Sign_key, On_ecdsa, On_rsa_simple, On_rsa_full, On_hmac) ->
case Sign_key of
{sign_ecdsa, Id, Curve, Digest_type, Public_key, Private_key} ->
On_ecdsa(Id, Curve, Digest_type, Public_key, Private_key);
{sign_hmac, Id@1, Digest_type@1, Secret} ->
On_hmac(Id@1, Digest_type@1, Secret);
{sign_rsa_full,
Id@2,
Digest_type@2,
Public_exponent,
Modulus,
Private_exponent,
First_prime_factor,
Second_prime_factor,
First_factor_crt_exponent,
Second_factor_crt_exponent,
First_crt_coefficient,
Other_primes_info,
Padding} ->
On_rsa_full(
Id@2,
Digest_type@2,
Public_exponent,
Modulus,
Private_exponent,
First_prime_factor,
Second_prime_factor,
First_factor_crt_exponent,
Second_factor_crt_exponent,
First_crt_coefficient,
Other_primes_info,
Padding
);
{sign_rsa_simple,
Id@3,
Digest_type@3,
Public_exponent@1,
Modulus@1,
Private_exponent@1,
Padding@1} ->
On_rsa_simple(
Id@3,
Digest_type@3,
Public_exponent@1,
Modulus@1,
Private_exponent@1,
Padding@1
)
end.
-file("src/ywt/sign_key.gleam", 537).
?DOC(false).
-spec to_jwk(sign_key()) -> gleam@json:json().
to_jwk(Key) ->
Fields@1 = case Key of
{sign_hmac, _, _, _} ->
[{<<"kty"/utf8>>, gleam@json:string(<<"oct"/utf8>>)},
{<<"k"/utf8>>,
ywt@internal@core:bits_to_json(erlang:element(4, Key))}];
{sign_ecdsa, _, _, _, _, _} ->
Coord_size = ywt@internal@core:named_curve_size(
erlang:element(3, Key)
),
{X@1, Y@1} = case erlang:element(5, Key) of
<<16#4, X:Coord_size/binary, Y:Coord_size/binary>> -> {X, Y};
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ywt/sign_key"/utf8>>,
function => <<"to_jwk"/utf8>>,
line => 545,
value => _assert_fail,
start => 18525,
'end' => 18620,
pattern_start => 18536,
pattern_end => 18595})
end,
[{<<"kty"/utf8>>, gleam@json:string(<<"EC"/utf8>>)},
{<<"crv"/utf8>>,
gleam@json:string(
ywt@internal@core:named_curve(erlang:element(3, Key))
)},
{<<"x"/utf8>>, ywt@internal@core:bits_to_json(X@1)},
{<<"y"/utf8>>, ywt@internal@core:bits_to_json(Y@1)},
{<<"d"/utf8>>,
ywt@internal@core:bits_to_json(erlang:element(6, Key))}];
{sign_rsa_simple, _, _, _, _, _, _} ->
[{<<"kty"/utf8>>, gleam@json:string(<<"RSA"/utf8>>)},
{<<"e"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(4, Key))},
{<<"n"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(5, Key))},
{<<"d"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(6, Key))}];
{sign_rsa_full, _, _, _, _, _, _, _, _, _, _, _, _} ->
Fields = [{<<"kty"/utf8>>, gleam@json:string(<<"RSA"/utf8>>)},
{<<"e"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(4, Key))},
{<<"n"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(5, Key))},
{<<"d"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(6, Key))},
{<<"p"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(7, Key))},
{<<"q"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(8, Key))},
{<<"dp"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(9, Key))},
{<<"dq"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(10, Key))},
{<<"qi"/utf8>>,
ywt@internal@core:int_to_json(erlang:element(11, Key))}],
case erlang:element(12, Key) of
[] ->
Fields;
[_ | _] ->
[{<<"oth"/utf8>>,
gleam@json:array(
erlang:element(12, Key),
fun(Info) ->
gleam@json:object(
[{<<"r"/utf8>>,
ywt@internal@core:int_to_json(
erlang:element(1, Info)
)},
{<<"d"/utf8>>,
ywt@internal@core:int_to_json(
erlang:element(2, Info)
)},
{<<"t"/utf8>>,
ywt@internal@core:int_to_json(
erlang:element(3, Info)
)}]
)
end
)} |
Fields]
end
end,
Fields@2 = [{<<"alg"/utf8>>, ywt@algorithm:to_json(algorithm(Key))} |
Fields@1],
Fields@3 = case erlang:element(2, Key) of
{some, Id} ->
[{<<"kid"/utf8>>, gleam@json:string(Id)} | Fields@2];
none ->
Fields@2
end,
gleam@json:object(Fields@3).