-module(kryptos@ecdsa). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/kryptos/ecdsa.gleam"). -export([sign/3, verify/4, der_to_rs/2, sign_rs/3, rs_to_der/2, verify_rs/4]). -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( " Elliptic Curve Digital Signature Algorithm (ECDSA).\n" "\n" " ECDSA provides digital signatures using elliptic curve cryptography,\n" " offering strong security with smaller key sizes compared to RSA.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import kryptos/ec\n" " import kryptos/ecdsa\n" " import kryptos/hash\n" "\n" " let #(private_key, public_key) = ec.generate_key_pair(ec.P256)\n" " let message = <<\"hello world\":utf8>>\n" " let signature = ecdsa.sign(private_key, message, hash.Sha256)\n" " let valid = ecdsa.verify(public_key, message, signature, hash.Sha256)\n" " // valid == True\n" " ```\n" ). -file("src/kryptos/ecdsa.gleam", 43). ?DOC( " Signs a message using ECDSA with the specified hash algorithm.\n" "\n" " The message is hashed internally using the provided algorithm before signing.\n" " Signatures may be non-deterministic depending on platform (Erlang uses random\n" " nonces, some platforms may use deterministic RFC 6979 nonces).\n" "\n" " ## Parameters\n" " - `private_key`: An elliptic curve private key from `ec.generate_key_pair`\n" " - `message`: The message to sign (any length)\n" " - `hash`: The hash algorithm to use (e.g., `Sha256`, `Sha384`, `Sha512`)\n" "\n" " ## Returns\n" " A DER-encoded ECDSA signature.\n" ). -spec sign(kryptos@ec:private_key(), bitstring(), kryptos@hash:hash_algorithm()) -> bitstring(). sign(Private_key, Message, Hash) -> kryptos_ffi:ecdsa_sign(Private_key, Message, Hash). -file("src/kryptos/ecdsa.gleam", 65). ?DOC( " Verifies an ECDSA signature against a message.\n" "\n" " The message is hashed internally using the provided algorithm before\n" " verification. The same hash algorithm used during signing must be used\n" " for verification.\n" "\n" " ## Parameters\n" " - `public_key`: The elliptic curve public key corresponding to the signing key\n" " - `message`: The original message that was signed\n" " - `signature`: The DER-encoded signature to verify\n" " - `hash`: The hash algorithm used during signing\n" "\n" " ## Returns\n" " `True` if the signature is valid, `False` otherwise.\n" ). -spec verify( kryptos@ec:public_key(), bitstring(), bitstring(), kryptos@hash:hash_algorithm() ) -> boolean(). verify(Public_key, Message, Signature, Hash) -> kryptos_ffi:ecdsa_verify(Public_key, Message, Signature, Hash). -file("src/kryptos/ecdsa.gleam", 133). ?DOC( " Converts a DER-encoded ECDSA signature to R||S format.\n" "\n" " R||S format concatenates the r and s integer values, each padded\n" " to the curve's coordinate size with leading zeros.\n" "\n" " ## Parameters\n" " - `der`: A DER-encoded ECDSA signature\n" " - `curve`: The elliptic curve used for the signature\n" "\n" " ## Returns\n" " `Ok(rs_signature)` on success, `Error(Nil)` if the DER is malformed\n" " or contains trailing garbage.\n" ). -spec der_to_rs(bitstring(), kryptos@ec:curve()) -> {ok, bitstring()} | {error, nil}. der_to_rs(Der_sig, Curve) -> Coord_size = kryptos@ec:coordinate_size(Curve), gleam@result:'try'( kryptos@internal@der:parse_sequence(Der_sig), fun(_use0) -> {Content, Remaining} = _use0, gleam@bool:guard( erlang:byte_size(Remaining) /= 0, {error, nil}, fun() -> gleam@result:'try'( kryptos@internal@der:parse_integer(Content), fun(_use0@1) -> {R_bytes, Remaining@1} = _use0@1, gleam@result:'try'( kryptos@internal@der:parse_integer(Remaining@1), fun(_use0@2) -> {S_bytes, Remaining@2} = _use0@2, gleam@bool:guard( erlang:byte_size(Remaining@2) /= 0, {error, nil}, fun() -> R = kryptos@internal@utils:strip_leading_zeros( R_bytes ), S = kryptos@internal@utils:strip_leading_zeros( S_bytes ), R_ok = erlang:byte_size(R) =< Coord_size, S_ok = erlang:byte_size(S) =< Coord_size, gleam@bool:guard( not R_ok orelse not S_ok, {error, nil}, fun() -> {ok, gleam_stdlib:bit_array_concat( [kryptos@internal@utils:pad_left( R, Coord_size ), kryptos@internal@utils:pad_left( S, Coord_size )] )} end ) end ) end ) end ) end ) end ). -file("src/kryptos/ecdsa.gleam", 84). ?DOC( " Signs a message and returns the signature in R||S format (IEEE P1363).\n" "\n" " In R||S format, the signature is the concatenation of r and s values,\n" " each padded to the curve's coordinate size.\n" "\n" " ## Parameters\n" " - `private_key`: An elliptic curve private key\n" " - `message`: The message to sign\n" " - `hash`: The hash algorithm to use\n" "\n" " ## Returns\n" " An R||S format signature (2 * coordinate_size bytes).\n" ). -spec sign_rs( kryptos@ec:private_key(), bitstring(), kryptos@hash:hash_algorithm() ) -> bitstring(). sign_rs(Private_key, Message, Hash) -> Der_sig = kryptos_ffi:ecdsa_sign(Private_key, Message, Hash), Curve = kryptos_ffi:ec_private_key_curve(Private_key), Rs_sig@1 = case der_to_rs(Der_sig, Curve) of {ok, Rs_sig} -> Rs_sig; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"kryptos/ecdsa"/utf8>>, function => <<"sign_rs"/utf8>>, line => 91, value => _assert_fail, start => 3048, 'end' => 3097, pattern_start => 3059, pattern_end => 3069}) end, Rs_sig@1. -file("src/kryptos/ecdsa.gleam", 171). ?DOC( " Converts an R||S format signature to DER encoding.\n" "\n" " ## Parameters\n" " - `rs`: An R||S format signature (2 * coordinate_size bytes)\n" " - `curve`: The elliptic curve used for the signature\n" "\n" " ## Returns\n" " `Ok(der_signature)` on success, `Error(Nil)` if input length is invalid.\n" ). -spec rs_to_der(bitstring(), kryptos@ec:curve()) -> {ok, bitstring()} | {error, nil}. rs_to_der(Rs, Curve) -> Coord_size = kryptos@ec:coordinate_size(Curve), gleam@bool:guard( erlang:byte_size(Rs) /= (Coord_size * 2), {error, nil}, fun() -> R@1 = case gleam_stdlib:bit_array_slice(Rs, 0, Coord_size) of {ok, R} -> R; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"kryptos/ecdsa"/utf8>>, function => <<"rs_to_der"/utf8>>, line => 179, value => _assert_fail, start => 5758, 'end' => 5811, pattern_start => 5769, pattern_end => 5774}) end, S@1 = case gleam_stdlib:bit_array_slice(Rs, Coord_size, Coord_size) of {ok, S} -> S; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"kryptos/ecdsa"/utf8>>, function => <<"rs_to_der"/utf8>>, line => 180, value => _assert_fail@1, start => 5814, 'end' => 5876, pattern_start => 5825, pattern_end => 5830}) end, _pipe = gleam_stdlib:bit_array_concat( [kryptos@internal@der:encode_integer(R@1), kryptos@internal@der:encode_integer(S@1)] ), _pipe@1 = kryptos@internal@der:encode_sequence(_pipe), {ok, _pipe@1} end ). -file("src/kryptos/ecdsa.gleam", 108). ?DOC( " Verifies an R||S format signature against a message.\n" "\n" " The R||S format is the concatenation of r and s values, each padded\n" " to the curve's coordinate size.\n" "\n" " ## Parameters\n" " - `public_key`: The public key corresponding to the signing key\n" " - `message`: The original message that was signed\n" " - `signature`: The R||S format signature to verify\n" " - `hash`: The hash algorithm used during signing\n" "\n" " ## Returns\n" " `True` if the signature is valid, `False` otherwise.\n" ). -spec verify_rs( kryptos@ec:public_key(), bitstring(), bitstring(), kryptos@hash:hash_algorithm() ) -> boolean(). verify_rs(Public_key, Message, Signature, Hash) -> Curve = kryptos_ffi:ec_public_key_curve(Public_key), case rs_to_der(Signature, Curve) of {ok, Der_sig} -> kryptos_ffi:ecdsa_verify(Public_key, Message, Der_sig, Hash); {error, nil} -> false end.