2021-08-04 00:40:09 +02:00
using System ;
using System.Collections.Generic ;
2021-08-10 08:25:37 +02:00
using System.Numerics ;
2021-09-18 07:26:06 +02:00
using System.Text.Json ;
using System.Text.Json.Serialization ;
2021-08-04 00:40:09 +02:00
namespace WTelegram
{
public static class Helpers
{
2021-11-12 06:10:26 +01:00
/// <summary>Callback for logging a line (string) with its associated <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel">severity level</see> (int)</summary>
2021-08-06 01:54:29 +02:00
public static Action < int , string > Log { get ; set ; } = DefaultLogger ;
2021-11-06 05:22:33 +01:00
/// <summary>For serializing indented Json with fields included</summary>
2021-12-04 00:14:15 +01:00
public static readonly JsonSerializerOptions JsonOptions = new ( ) { IncludeFields = true , WriteIndented = true , IgnoreReadOnlyProperties = true } ;
2021-08-04 00:40:09 +02:00
public static V GetOrCreate < K , V > ( this Dictionary < K , V > dictionary , K key ) where V : new ( )
= > dictionary . TryGetValue ( key , out V value ) ? value : dictionary [ key ] = new V ( ) ;
2021-08-04 10:11:07 +02:00
private static readonly ConsoleColor [ ] LogLevelToColor = new [ ] { ConsoleColor . DarkGray , ConsoleColor . DarkCyan , ConsoleColor . Cyan ,
ConsoleColor . Yellow , ConsoleColor . Red , ConsoleColor . Magenta , ConsoleColor . DarkBlue } ;
private static void DefaultLogger ( int level , string message )
{
Console . ForegroundColor = LogLevelToColor [ level ] ;
Console . WriteLine ( message ) ;
Console . ResetColor ( ) ;
}
2021-11-06 05:22:33 +01:00
/// <summary>Get a cryptographic random 64-bit value</summary>
2021-08-05 16:29:58 +02:00
public static long RandomLong ( )
{
2021-08-16 22:30:45 +02:00
#if NETCOREAPP2_1_OR_GREATER
2021-08-05 16:29:58 +02:00
Span < long > span = stackalloc long [ 1 ] ;
System . Security . Cryptography . RandomNumberGenerator . Fill ( System . Runtime . InteropServices . MemoryMarshal . AsBytes ( span ) ) ;
return span [ 0 ] ;
2021-08-16 22:30:45 +02:00
#else
var span = new byte [ 8 ] ;
Encryption . RNG . GetBytes ( span ) ;
return BitConverter . ToInt64 ( span , 0 ) ;
#endif
2021-08-05 16:29:58 +02:00
}
2021-11-06 05:22:33 +01:00
internal static byte [ ] ToBigEndian ( ulong value ) // variable-size buffer
2021-08-04 00:40:09 +02:00
{
int i ;
var temp = value ;
2021-08-20 02:13:58 +02:00
for ( i = 1 ; ( temp > > = 8 ) ! = 0 ; i + + ) ;
2021-08-04 00:40:09 +02:00
var result = new byte [ i ] ;
while ( - - i > = 0 ) { result [ i ] = ( byte ) value ; value > > = 8 ; }
return result ;
}
2021-11-06 05:22:33 +01:00
internal static ulong FromBigEndian ( byte [ ] bytes ) // variable-size buffer
2021-08-04 00:40:09 +02:00
{
2021-08-06 01:54:29 +02:00
if ( bytes . Length > 8 ) throw new ArgumentException ( $"expected bytes length <= 8 but got {bytes.Length}" ) ;
2021-08-04 00:40:09 +02:00
ulong result = 0 ;
foreach ( byte b in bytes )
result = ( result < < 8 ) + b ;
return result ;
}
2021-08-20 02:13:58 +02:00
internal static byte [ ] To256Bytes ( this BigInteger bi )
{
var bigEndian = bi . ToByteArray ( true , true ) ;
if ( bigEndian . Length = = 256 ) return bigEndian ;
var result = new byte [ 256 ] ;
bigEndian . CopyTo ( result , 256 - bigEndian . Length ) ;
return result ;
}
2021-08-07 06:23:13 +02:00
internal static ulong PQFactorize ( ulong pq ) // ported from https://github.com/tdlib/td/blob/master/tdutils/td/utils/crypto.cpp#L90
2021-08-04 00:40:09 +02:00
{
if ( pq < 2 ) return 1 ;
var random = new Random ( ) ;
ulong g = 0 ;
for ( int i = 0 , iter = 0 ; i < 3 | | iter < 1000 ; i + + )
{
ulong q = ( ulong ) random . Next ( 17 , 32 ) % ( pq - 1 ) ;
ulong x = ( ( ulong ) random . Next ( ) + ( ulong ) random . Next ( ) < < 31 ) % ( pq - 1 ) + 1 ;
ulong y = x ;
int lim = 1 < < ( Math . Min ( 5 , i ) + 18 ) ;
for ( int j = 1 ; j < lim ; j + + )
{
iter + + ;
ulong a = x ;
ulong b = x ;
ulong c = q ;
// c += a * b
while ( b ! = 0 )
{
if ( ( b & 1 ) ! = 0 )
{
c + = a ;
if ( c > = pq )
c - = pq ;
}
a + = a ;
if ( a > = pq )
a - = pq ;
b > > = 1 ;
}
x = c ;
ulong z = x < y ? pq + x - y : x - y ;
g = gcd ( z , pq ) ;
if ( g ! = 1 )
break ;
if ( ( j & ( j - 1 ) ) = = 0 )
y = x ;
}
if ( g > 1 & & g < pq )
break ;
}
if ( g ! = 0 )
{
ulong other = pq / g ;
if ( other < g )
g = other ;
}
return g ;
static ulong gcd ( ulong a , ulong b )
{
if ( a = = 0 ) return b ;
if ( b = = 0 ) return a ;
int shift = 0 ;
while ( ( a & 1 ) = = 0 & & ( b & 1 ) = = 0 )
{
a > > = 1 ;
b > > = 1 ;
shift + + ;
}
while ( true )
{
while ( ( a & 1 ) = = 0 )
a > > = 1 ;
while ( ( b & 1 ) = = 0 )
b > > = 1 ;
if ( a > b )
a - = b ;
else if ( b > a )
b - = a ;
else
return a < < shift ;
}
}
}
2021-08-10 08:25:37 +02:00
2021-08-20 02:33:43 +02:00
public static int MillerRabinIterations { get ; set ; } = 64 ; // 64 is OpenSSL default for 2048-bits numbers
2021-09-01 23:03:04 +02:00
private static readonly HashSet < BigInteger > GoodPrimes = new ( ) ;
2021-11-06 05:22:33 +01:00
/// <summary>Miller– Rabin primality test</summary>
/// <param name="n">The number to check for primality</param>
2021-08-20 02:33:43 +02:00
public static bool IsProbablePrime ( this BigInteger n )
2021-08-10 08:25:37 +02:00
{
2021-08-20 02:13:58 +02:00
var n_minus_one = n - BigInteger . One ;
if ( n_minus_one . Sign < = 0 ) return false ;
2021-09-01 23:03:04 +02:00
if ( GoodPrimes . Contains ( n ) ) return true ;
2021-08-20 02:13:58 +02:00
int s ;
var d = n_minus_one ;
for ( s = 0 ; d . IsEven ; s + + ) d > > = 1 ;
var bitLen = n . GetBitLength ( ) ;
var randomBytes = new byte [ bitLen / 8 + 1 ] ;
var lastByteMask = ( byte ) ( ( 1 < < ( int ) ( bitLen % 8 ) ) - 1 ) ;
BigInteger a ;
2021-08-20 02:33:43 +02:00
if ( MillerRabinIterations < 15 ) // 15 is the minimum recommended by Telegram
Log ( 3 , $"MillerRabinIterations ({MillerRabinIterations}) is below the minimal level of safety (15)" ) ;
for ( int i = 0 ; i < MillerRabinIterations ; i + + )
2021-08-20 02:13:58 +02:00
{
do
{
Encryption . RNG . GetBytes ( randomBytes ) ;
randomBytes [ ^ 1 ] & = lastByteMask ; // we don't want more bits than necessary
a = new BigInteger ( randomBytes ) ;
}
while ( a < 3 | | a > = n_minus_one ) ;
a - - ;
var x = BigInteger . ModPow ( a , d , n ) ;
if ( x . IsOne | | x = = n_minus_one ) continue ;
int r ;
for ( r = s - 1 ; r > 0 ; r - - )
{
x = BigInteger . ModPow ( x , 2 , n ) ;
if ( x . IsOne ) return false ;
if ( x = = n_minus_one ) break ;
}
if ( r = = 0 ) return false ;
}
2021-09-01 23:03:04 +02:00
GoodPrimes . Add ( n ) ;
2021-08-20 02:13:58 +02:00
return true ;
2021-08-10 08:25:37 +02:00
}
2021-08-04 00:40:09 +02:00
}
}