From da0dc74cd1c99e5212c09e5c7d6be29c2efd341c Mon Sep 17 00:00:00 2001 From: bazzilic Date: Tue, 7 Dec 2021 00:10:45 +0800 Subject: [PATCH] fixes to GenRandomBits() to ensure it generates exact number of bits + better tests --- src/Aprismatic.BigIntegerExt/BigIntegerExt.cs | 55 +++++---- .../BigIntegerExtTest.csproj | 4 +- test/BigIntegerExtTest/BigIntegerExtTests.cs | 112 ++++++++++++------ 3 files changed, 108 insertions(+), 63 deletions(-) diff --git a/src/Aprismatic.BigIntegerExt/BigIntegerExt.cs b/src/Aprismatic.BigIntegerExt/BigIntegerExt.cs index 49b3101..a18f89f 100644 --- a/src/Aprismatic.BigIntegerExt/BigIntegerExt.cs +++ b/src/Aprismatic.BigIntegerExt/BigIntegerExt.cs @@ -42,7 +42,7 @@ public static class BigIntegerExt /// Modulo inverse of this; or 1 if mod.inv. does not exist. public static BigInteger ModInverse(this BigInteger T, BigInteger mod) { - BigInteger i = mod, v = 0, d = 1, t, x; + BigInteger i = mod, v = BigInteger.Zero, d = BigInteger.One, t, x; while (T.Sign > 0) { @@ -54,16 +54,16 @@ public static BigInteger ModInverse(this BigInteger T, BigInteger mod) v = x; } - v %= mod; + v %= mod; // TODO: this could be faster (?) if (v.Sign < 0) - v = (v + mod) % mod; + v = (v + mod) % mod; // TODO: this too (?) return v; } /// - /// Returns the position of the most significant bit in the BigInteger + /// Returns the position of the most significant bit of the BigInteger's absolute value. /// /// /// 1) The result is 1, if the value of BigInteger is 0...0000 0000 @@ -75,7 +75,7 @@ public static BigInteger ModInverse(this BigInteger T, BigInteger mod) /// public static int BitCount(this BigInteger T) { - var data = T.ToByteArray(); + var data = T.Sign >= 0 ? T.ToByteArray() : (-T).ToByteArray(); byte value = data[data.Length - 1]; byte mask = 0x80; var bits = 8; @@ -102,8 +102,10 @@ public static BigInteger GenRandomBits(this BigInteger T, int bits, RandomNumber if (bits <= 0) throw new ArithmeticException("Number of required bits is not valid."); + bits++; // add one for the sign + var bytes = bits >> 3; - var remBits = bits % 8; + var remBits = bits % 8; // TODO: this can be made faster if (remBits != 0) bytes++; @@ -112,23 +114,24 @@ public static BigInteger GenRandomBits(this BigInteger T, int bits, RandomNumber rng.GetBytes(data); - if (remBits != 0) + if (remBits == 1) // bytes must be != 0 here due to bits++ { - byte mask; - - if (bits != 1) - { - mask = (byte) (0x01 << (remBits - 1)); - data[bytes - 1] |= mask; - } - - mask = (byte) (0xFF >> (8 - remBits)); - data[bytes - 1] &= mask; + data[bytes - 1] = 0; // added byte set 0 for positive sign + data[bytes - 2] |= 0x80; // MSB set to 1 } - else - data[bytes - 1] |= 0x80; + else if (remBits > 1) + { + var mask = (byte)(0x01 << (remBits - 2)); + data[bytes - 1] |= mask; - data[bytes - 1] &= 0x7F; + mask = (byte)(0xFF >> (8 - remBits + 1)); + data[bytes - 1] &= mask; // set the sign bit to 0 + } + else // remBits == 0 + { + data[bytes - 1] |= 0x40; // MSB to 1 + data[bytes - 1] &= 0x7F; // Sign to 0 + } return new BigInteger(data); } @@ -152,7 +155,7 @@ public static BigInteger GenPseudoPrime(this BigInteger T, int bits, int confide while (!done) { result = result.GenRandomBits(bits, rand); - result |= 1; // make it odd + result |= BigInteger.One; // make it odd // prime test done = result.IsProbablePrime(confidence); @@ -177,7 +180,7 @@ public static bool IsProbablePrime(this BigInteger T, int confidence) if (thisVal <= UInt64.MaxValue) { - var uival = (UInt64) thisVal; + var uival = (UInt64)thisVal; for (var i = 0; i < PrimesBelow2000.Length; i++) // test for divisibility by primes < 2000 @@ -243,10 +246,10 @@ public static bool RabinMillerTest(this BigInteger w, int confidence) do { b = BigInteger.Zero.GenRandomBits(wlen, rng); - } while (b >= w - 1 || b < 2); + } while (b >= w - BigInteger.One || b < 2); var z = BigInteger.ModPow(b, m, w); - if (z.IsOne || z == w - 1) + if (z.IsOne || z == w - BigInteger.One) continue; for (var j = 1; j < a; j++) @@ -254,11 +257,11 @@ public static bool RabinMillerTest(this BigInteger w, int confidence) z = BigInteger.ModPow(z, 2, w); if (z.IsOne) return false; - if (z == w - 1) + if (z == w - BigInteger.One) break; } - if (z != w - 1) + if (z != w - BigInteger.One) return false; } diff --git a/test/BigIntegerExtTest/BigIntegerExtTest.csproj b/test/BigIntegerExtTest/BigIntegerExtTest.csproj index 46a4c21..ef52a7b 100644 --- a/test/BigIntegerExtTest/BigIntegerExtTest.csproj +++ b/test/BigIntegerExtTest/BigIntegerExtTest.csproj @@ -3,9 +3,9 @@ netcoreapp3.1 - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/BigIntegerExtTest/BigIntegerExtTests.cs b/test/BigIntegerExtTest/BigIntegerExtTests.cs index 8b42180..4968e4f 100644 --- a/test/BigIntegerExtTest/BigIntegerExtTests.cs +++ b/test/BigIntegerExtTest/BigIntegerExtTests.cs @@ -9,17 +9,43 @@ namespace BigIntegerExtTests { public class BigIntegerExtTests { + [Fact(DisplayName = "BitCount")] + public void TestBitCount() + { + Assert.Equal(1, BigInteger.Zero.BitCount()); + Assert.Equal(1, BigInteger.One.BitCount()); + Assert.Equal(1, BigInteger.MinusOne.BitCount()); + + var b1 = new BigInteger(Int32.MaxValue); + var b2 = new BigInteger(Int32.MinValue); + Assert.Equal(31, b1.BitCount()); + Assert.Equal(32, b2.BitCount()); + Assert.Equal(32, (b1 + 1).BitCount()); + + var b3 = new BigInteger(UInt32.MaxValue); + Assert.Equal(32, b3.BitCount()); + + var b4 = new BigInteger(7); + var b5 = new BigInteger(8); + Assert.Equal(3, b4.BitCount()); + Assert.Equal(4, b5.BitCount()); + } + [Fact(DisplayName = "ModInverse")] public void TestModInverse() { { var a = new BigInteger[] { 0, 0, 1, 3, 7, 25, 2, 13, 19, 31, 3 }; - var m = new BigInteger[] { 1000000007, 1999, 2, 6, 87, 87, 91, 91, 1212393831, 73714876143, 73714876143 }; + var m = new BigInteger[] + { 1000000007, 1999, 2, 6, 87, 87, 91, 91, 1212393831, 73714876143, 73714876143 }; var r = new BigInteger[] { 736445995, 1814, 1, 1, 25, 7, 46, 1, 701912218, 45180085378, 1 }; var t = new BigInteger(); - BigInteger.TryParse("470782681346529800216759025446747092045188631141622615445464429840250748896490263346676188477401449398784352124574498378830506322639352584202116605974693692194824763263949618703029846313252400361025245824301828641617858127932941468016666971398736792667282916657805322080902778987073711188483372360907612588995664533157503380846449774089269965646418521613225981431666593065726252482995754339317299670566915780168", out t); - a[0] = t; a[1] = t; + BigInteger.TryParse( + "470782681346529800216759025446747092045188631141622615445464429840250748896490263346676188477401449398784352124574498378830506322639352584202116605974693692194824763263949618703029846313252400361025245824301828641617858127932941468016666971398736792667282916657805322080902778987073711188483372360907612588995664533157503380846449774089269965646418521613225981431666593065726252482995754339317299670566915780168", + out t); + a[0] = t; + a[1] = t; Assert.Equal(a.Length, m.Length); Assert.Equal(m.Length, r.Length); @@ -45,6 +71,7 @@ public void TestModInverse() bi = bi.GenRandomBits(rnd.Next(1, 1024), rng); j = 0; } + mod = mod.GenRandomBits(rnd.Next(1, 128), rng); } @@ -64,76 +91,91 @@ public void TestIsProbablePrime() for (var i = 2UL; i < 2000; i++) // since we have an array of primes below 2000 that we can check against { var res = (new BigInteger(i)).IsProbablePrime(10); - Assert.True(BigIntegerExt.PrimesBelow2000.Contains(i) == res, $"{i} is prime is {BigIntegerExt.PrimesBelow2000.Contains(i)} but was evaluated as {res}"); + Assert.True(BigIntegerExt.PrimesBelow2000.Contains(i) == res, + $"{i} is prime is {BigIntegerExt.PrimesBelow2000.Contains(i)} but was evaluated as {res}"); } - foreach (var p in new[] { 633910111, 838041647, 15485863, 452930477, 28122569887267, 29996224275833, 571245373823500631 }) + foreach (var p in new[] + { 633910111, 838041647, 15485863, 452930477, 28122569887267, 29996224275833, 571245373823500631 }) { - Assert.True((new BigInteger(p)).IsProbablePrime(10)); + Assert.True(new BigInteger(p).IsProbablePrime(10)); } foreach (var p in new[] { 398012025725459, 60030484763, 571245373823500630 }) { - Assert.False((new BigInteger(p)).IsProbablePrime(50)); + Assert.False(new BigInteger(p).IsProbablePrime(50)); } } - [Fact(DisplayName = "SecuredGenRandomBits")] - public void TestSecuredGenRandomBits() + [Fact(DisplayName = "GenPseudoPrime")] + public void TestGenPseudoPrime() { + var bi = new BigInteger(); var rng = RandomNumberGenerator.Create(); var rand = new Random(); - for (var i = 0; i < 9999; i++) - { // Test < 32 bits + // Test arbitrary values + for (var i = 0; i < 200; i++) + { + var prime = bi.GenPseudoPrime(rand.Next(2, 768), 2, rng); + + foreach (var pr in BigIntegerExt.PrimesBelow2000) + { + Assert.True(prime == pr || prime % pr != 0, + $"prime: {prime}{Environment.NewLine}" + + $"pr: {pr}"); + } + } + } + + [Fact(DisplayName = "GenRandomBits")] + public void TestGenRandomBits() + { + var rng = RandomNumberGenerator.Create(); + var rand = new Random(); + + for (var i = 0; i < 9999; i++) // Test < 32 bits + { var bi = new BigInteger(); bi = bi.GenRandomBits(rand.Next(1, 33), rng); var bytes = bi.ToByteArray(); - var new_bytes = new byte[4]; + var new_bytes = new byte[5]; Array.Copy(bytes, new_bytes, bytes.Length); - Assert.True(BitConverter.ToUInt32(new_bytes, 0) < (Math.Pow(2, 32) - 1)); + Assert.True(BitConverter.ToUInt32(new_bytes, 0) < Math.Pow(2, 32)); } - // Test on random number of bits - for (var i = 0; i < 9999; i++) + for (var i = 0; i < 9999; i++) // Test on random number of bits { var bi = new BigInteger(); var bits = rand.Next(1, 70 * 32 + 1); bi = bi.GenRandomBits(bits, rng); - Assert.True(bits >= bi.BitCount()); + Assert.True(bits == bi.BitCount()); Assert.True(bi >= 0); } - for (var i = 0; i < 9999; i++) - { // Test lower boudary value + for (var i = 0; i < 9999; i++) // Test lower boundary value + { var bi = new BigInteger(); bi = bi.GenRandomBits(1, rng); Assert.True(bi.ToByteArray()[0] == 1 || bi.ToByteArray()[0] == 0); } - } - - [Fact(DisplayName = "GenPseudoPrime")] - public void TestGenPseudoPrime() - { - var bi = new BigInteger(); - var rng = RandomNumberGenerator.Create(); - var rand = new Random(); - // Test arbitrary values - for (var i = 0; i < 200; i++) + for (var i = 0; i < 9999; i++) // some edge cases { - var prime = bi.GenPseudoPrime(rand.Next(2, 768), 2, rng); + var bi = new BigInteger(); - foreach (var pr in BigIntegerExt.PrimesBelow2000) - { - Assert.True(prime == pr || prime % pr != 0, - $"prime: {prime}{Environment.NewLine}" + - $"pr: {pr}"); - } + var bi256 = bi.GenRandomBits(256, rng); + Assert.Equal(256, bi256.BitCount()); + + var bi257 = bi.GenRandomBits(257, rng); + Assert.Equal(257, bi257.BitCount()); + + var bi255 = bi.GenRandomBits(255, rng); + Assert.Equal(255, bi255.BitCount()); } } }