-module(gleeth@crypto@random). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/gleeth/crypto/random.gleam"). -export([generate_secure_bytes/1, generate_private_key_with_retries/1, generate_private_key/0, test_randomness_quality/2, error_to_string/1, is_retryable_error/1, test_random_availability/0]). -export_type([random_error/0, randomness_test_result/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. -type random_error() :: {system_random_not_available, binary()} | {insufficient_entropy, binary()} | {invalid_length, binary()} | {cryptographic_error, binary()} | {generation_failed, binary()}. -type randomness_test_result() :: {randomness_test_result, integer(), integer(), boolean(), boolean(), boolean(), float()}. -file("src/gleeth/crypto/random.gleam", 51). ?DOC( " Generate cryptographically secure random bytes of specified length\n" " Uses system's cryptographically secure random number generator\n" "\n" " ## Implementation Details\n" "\n" " - **Erlang/BEAM**: Uses `:crypto.strong_rand_bytes/1` from OTP crypto module\n" " - **JavaScript**: Uses Web Crypto API `crypto.getRandomValues()`\n" "\n" " Both implementations provide cryptographically secure pseudorandom number\n" " generation suitable for cryptographic key material.\n" "\n" " ## Parameters\n" "\n" " * `length` - Number of random bytes to generate (must be positive)\n" "\n" " ## Returns\n" "\n" " * `Ok(BitArray)` - Generated random bytes\n" " * `Error(RandomError)` - If generation fails\n" "\n" " ## Example\n" "\n" " ```gleam\n" " // Generate 32 random bytes for a private key\n" " case generate_secure_bytes(32) {\n" " Ok(random_bytes) -> // Use the random bytes\n" " Error(err) -> // Handle error\n" " }\n" " ```\n" "\n" " For Erlang: directly calls crypto:strong_rand_bytes/1 and wraps in Ok\n" " For JavaScript: calls FFI that returns Result type\n" ). -spec generate_secure_bytes_ffi(integer()) -> {ok, bitstring()} | {error, binary()}. generate_secure_bytes_ffi(Length) -> gleeth_ffi:generate_secure_bytes(Length). -file("src/gleeth/crypto/random.gleam", 59). -spec generate_secure_bytes(integer()) -> {ok, bitstring()} | {error, random_error()}. generate_secure_bytes(Length) -> case Length =< 0 of true -> {error, {invalid_length, <<"Length must be positive, got: "/utf8, (gleam@string:inspect(Length))/binary>>}}; false -> case generate_secure_bytes_ffi(Length) of {ok, Bytes} -> case erlang:byte_size(Bytes) of Size when Size =:= Length -> {ok, Bytes}; Actual_size -> {error, {generation_failed, <<<<<<"Expected "/utf8, (gleam@string:inspect(Length))/binary>>/binary, " bytes, got "/utf8>>/binary, (gleam@string:inspect(Actual_size))/binary>>}} end; {error, Msg} -> {error, {system_random_not_available, Msg}} end end. -file("src/gleeth/crypto/random.gleam", 130). ?DOC(" Internal recursive function to attempt private key generation\n"). -spec generate_private_key_attempt(integer(), integer()) -> {ok, gleeth@crypto@secp256k1:private_key()} | {error, random_error()}. generate_private_key_attempt(Max_retries, Current_attempt) -> case Current_attempt >= Max_retries of true -> {error, {generation_failed, <<<<"Failed to generate valid private key after "/utf8, (gleam@string:inspect(Max_retries))/binary>>/binary, " attempts"/utf8>>}}; false -> gleam@result:'try'( generate_secure_bytes(32), fun(Random_bytes) -> case gleeth@crypto@secp256k1:private_key_from_bytes( Random_bytes ) of {ok, Private_key} -> case gleeth@crypto@secp256k1:is_valid_private_key( Private_key ) of true -> {ok, Private_key}; false -> generate_private_key_attempt( Max_retries, Current_attempt + 1 ) end; {error, _} -> generate_private_key_attempt( Max_retries, Current_attempt + 1 ) end end ) end. -file("src/gleeth/crypto/random.gleam", 123). ?DOC( " Generate a private key with specified retry attempts\n" " This is useful for testing or when you want control over retry behavior\n" ). -spec generate_private_key_with_retries(integer()) -> {ok, gleeth@crypto@secp256k1:private_key()} | {error, random_error()}. generate_private_key_with_retries(Max_retries) -> generate_private_key_attempt(Max_retries, 0). -file("src/gleeth/crypto/random.gleam", 117). ?DOC( " Generate a cryptographically secure secp256k1 private key\n" "\n" " This function generates a random 32-byte private key that is guaranteed to be:\n" " - Cryptographically secure (using system CSPRNG)\n" " - Within the valid secp256k1 curve order\n" " - Non-zero (not the invalid all-zeros key)\n" "\n" " The function will retry generation if the random bytes don't form a valid\n" " private key (extremely rare but theoretically possible).\n" "\n" " ## Returns\n" "\n" " * `Ok(PrivateKey)` - A valid secp256k1 private key\n" " * `Error(RandomError)` - If generation fails after maximum retries\n" "\n" " ## Example\n" "\n" " ```gleam\n" " case generate_private_key() {\n" " Ok(private_key) -> {\n" " // Use the private key to create a wallet\n" " wallet.from_private_key_bytes(secp256k1.private_key_to_bytes(private_key))\n" " }\n" " Error(err) -> // Handle generation error\n" " }\n" " ```\n" ). -spec generate_private_key() -> {ok, gleeth@crypto@secp256k1:private_key()} | {error, random_error()}. generate_private_key() -> generate_private_key_with_retries(10). -file("src/gleeth/crypto/random.gleam", 209). ?DOC(" Generate multiple random samples for testing\n"). -spec generate_multiple_samples(integer(), integer(), list(bitstring())) -> {ok, list(bitstring())} | {error, random_error()}. generate_multiple_samples(Remaining, Byte_length, Acc) -> case Remaining of 0 -> {ok, Acc}; _ -> gleam@result:'try'( generate_secure_bytes(Byte_length), fun(Sample) -> generate_multiple_samples( Remaining - 1, Byte_length, [Sample | Acc] ) end ) end. -file("src/gleeth/crypto/random.gleam", 265). ?DOC(" Create a byte array of all zeros\n"). -spec create_all_zeros(integer()) -> bitstring(). create_all_zeros(Length) -> case Length of 0 -> <<>>; _ -> gleam_stdlib:bit_array_concat([<<0>>, create_all_zeros(Length - 1)]) end. -file("src/gleeth/crypto/random.gleam", 273). ?DOC(" Create a byte array of all ones (0xFF bytes)\n"). -spec create_all_ones(integer()) -> bitstring(). create_all_ones(Length) -> case Length of 0 -> <<>>; _ -> gleam_stdlib:bit_array_concat( [<<255>>, create_all_ones(Length - 1)] ) end. -file("src/gleeth/crypto/random.gleam", 311). ?DOC(" Count 1-bits in a single byte\n"). -spec count_bits_in_byte(integer(), integer()) -> integer(). count_bits_in_byte(Byte, Acc) -> case Byte of 0 -> Acc; _ -> New_acc = case Byte rem 2 of 1 -> Acc + 1; _ -> Acc end, count_bits_in_byte(Byte div 2, New_acc) end. -file("src/gleeth/crypto/random.gleam", 292). ?DOC(" Count 1-bits in a single BitArray\n"). -spec count_one_bits_in_sample(bitstring(), integer()) -> integer(). count_one_bits_in_sample(Sample, Acc) -> case erlang:byte_size(Sample) of 0 -> Acc; _ -> case gleam_stdlib:bit_array_slice(Sample, 0, 1) of {ok, <>} -> Bits = count_bits_in_byte(Byte, 0), case gleam_stdlib:bit_array_slice( Sample, 1, erlang:byte_size(Sample) - 1 ) of {ok, Rest} -> count_one_bits_in_sample(Rest, Acc + Bits); {error, _} -> Acc + Bits end; _ -> Acc end end. -file("src/gleeth/crypto/random.gleam", 281). ?DOC(" Count total number of 1-bits in a list of BitArrays\n"). -spec count_total_one_bits(list(bitstring()), integer()) -> integer(). count_total_one_bits(Samples, Acc) -> case Samples of [] -> Acc; [Sample | Rest] -> One_bits = count_one_bits_in_sample(Sample, 0), count_total_one_bits(Rest, Acc + One_bits) end. -file("src/gleeth/crypto/random.gleam", 329). -spec list_length_acc(list(any()), integer()) -> integer(). list_length_acc(List, Acc) -> case List of [] -> Acc; [_ | Rest] -> list_length_acc(Rest, Acc + 1) end. -file("src/gleeth/crypto/random.gleam", 325). ?DOC(" Get length of a list\n"). -spec list_length(list(any())) -> integer(). list_length(List) -> list_length_acc(List, 0). -file("src/gleeth/crypto/random.gleam", 337). ?DOC(" Check if list contains element\n"). -spec list_contains(list(LDB), LDB) -> boolean(). list_contains(List, Element) -> case List of [] -> false; [Head | Rest] -> case Head =:= Element of true -> true; false -> list_contains(Rest, Element) end end. -file("src/gleeth/crypto/random.gleam", 353). -spec list_unique_acc(list(LDG), list(LDG)) -> list(LDG). list_unique_acc(List, Acc) -> case List of [] -> Acc; [Head | Rest] -> case list_contains(Acc, Head) of true -> list_unique_acc(Rest, Acc); false -> list_unique_acc(Rest, [Head | Acc]) end end. -file("src/gleeth/crypto/random.gleam", 349). ?DOC(" Remove duplicates from list (simple O(n²) implementation)\n"). -spec list_unique(list(LDD)) -> list(LDD). list_unique(List) -> list_unique_acc(List, []). -file("src/gleeth/crypto/random.gleam", 365). ?DOC(" Convert Int to Float (helper function)\n"). -spec int_to_float(integer()) -> float(). int_to_float(Value) -> case Value of 0 -> +0.0; 1 -> 1.0; 2 -> 2.0; 3 -> 3.0; 4 -> 4.0; 5 -> 5.0; 6 -> 6.0; 7 -> 7.0; 8 -> 8.0; 9 -> 9.0; _ -> case Value > 0 of true -> int_to_float(Value div 2) * 2.0; false -> int_to_float(- Value) * -1.0 end end. -file("src/gleeth/crypto/random.gleam", 224). ?DOC(" Analyze randomness samples for basic statistical properties\n"). -spec analyze_randomness_samples(list(bitstring())) -> randomness_test_result(). analyze_randomness_samples(Samples) -> Sample_count = list_length(Samples), Byte_length = case Samples of [First | _] -> erlang:byte_size(First); [] -> 0 end, Unique_samples = list_unique(Samples), All_different = list_length(Unique_samples) =:= Sample_count, All_zeros = create_all_zeros(Byte_length), All_ones = create_all_ones(Byte_length), No_all_zeros = not list_contains(Samples, All_zeros), No_all_ones = not list_contains(Samples, All_ones), Total_bits = (Sample_count * Byte_length) * 8, Total_one_bits = count_total_one_bits(Samples, 0), Average_bit_density = case Total_bits of 0 -> +0.0; _ -> case int_to_float(Total_bits) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> int_to_float(Total_one_bits) / Gleam@denominator end end, {randomness_test_result, Sample_count, Byte_length, All_different, No_all_zeros, No_all_ones, Average_bit_density}. -file("src/gleeth/crypto/random.gleam", 176). ?DOC( " Test the quality of system randomness by generating multiple samples\n" " and checking for basic statistical properties. This is useful for\n" " development and debugging.\n" "\n" " Note: This is not a comprehensive randomness test - for production\n" " systems, use proper statistical test suites like NIST SP 800-22.\n" ). -spec test_randomness_quality(integer(), integer()) -> {ok, randomness_test_result()} | {error, random_error()}. test_randomness_quality(Sample_count, Byte_length) -> case (Sample_count =< 0) orelse (Byte_length =< 0) of true -> {error, {invalid_length, <<"Sample count and byte length must be positive"/utf8>>}}; false -> gleam@result:'try'( generate_multiple_samples(Sample_count, Byte_length, []), fun(Samples) -> {ok, analyze_randomness_samples(Samples)} end ) end. -file("src/gleeth/crypto/random.gleam", 395). ?DOC(" Convert RandomError to string for display\n"). -spec error_to_string(random_error()) -> binary(). error_to_string(Error) -> case Error of {system_random_not_available, Msg} -> <<"System random not available: "/utf8, Msg/binary>>; {insufficient_entropy, Msg@1} -> <<"Insufficient entropy: "/utf8, Msg@1/binary>>; {invalid_length, Msg@2} -> <<"Invalid length: "/utf8, Msg@2/binary>>; {cryptographic_error, Msg@3} -> <<"Cryptographic error: "/utf8, Msg@3/binary>>; {generation_failed, Msg@4} -> <<"Generation failed: "/utf8, Msg@4/binary>> end. -file("src/gleeth/crypto/random.gleam", 406). ?DOC(" Check if an error indicates a temporary failure that might succeed on retry\n"). -spec is_retryable_error(random_error()) -> boolean(). is_retryable_error(Error) -> case Error of {system_random_not_available, _} -> false; {insufficient_entropy, _} -> true; {invalid_length, _} -> false; {cryptographic_error, _} -> true; {generation_failed, _} -> false end. -file("src/gleeth/crypto/random.gleam", 422). ?DOC(" Check if the random system is available and working\n"). -spec test_random_availability() -> {ok, nil} | {error, random_error()}. test_random_availability() -> gleam@result:'try'(generate_secure_bytes(1), fun(_) -> {ok, nil} end).