From bca94571527f9fd1f808a5e9fec61005749c4f38 Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Mon, 14 Oct 2024 19:01:08 +0300 Subject: [PATCH 1/2] MultiplicativeGroupAlgebra.CreateCryptoGroup skips more known non-prime candidates. Sophie Germain primes can only have a value of 5 modulo 6, which is used to improve the prime finding algorithm in MultiplicativeGroupAlgebra.CreateCryptoGroup, which previously was checking every odd number. This reduces the number of primality tests that are performed by up to 2/3. Also joins the previously separate test cases into a single implementation and fixes a bug in which the rng mock was not returning the expected bytes. --- .../MultiplicativeGroupAlgebraTests.cs | 54 ++++++------------- .../MultiplicativeGroupAlgebra.cs | 16 ++++-- 2 files changed, 27 insertions(+), 43 deletions(-) diff --git a/CompactCryptoGroupAlgebra.Tests/Multiplicative/MultiplicativeGroupAlgebraTests.cs b/CompactCryptoGroupAlgebra.Tests/Multiplicative/MultiplicativeGroupAlgebraTests.cs index 26d8cfe..db604a3 100644 --- a/CompactCryptoGroupAlgebra.Tests/Multiplicative/MultiplicativeGroupAlgebraTests.cs +++ b/CompactCryptoGroupAlgebra.Tests/Multiplicative/MultiplicativeGroupAlgebraTests.cs @@ -1,6 +1,6 @@ // CompactCryptoGroupAlgebra - C# implementation of abelian group algebra for experimental cryptography -// SPDX-FileCopyrightText: 2022 Lukas Prediger +// SPDX-FileCopyrightText: 2022-2024 Lukas Prediger // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileType: SOURCE @@ -299,60 +299,36 @@ public void TestCreateCryptoGroup() } [Test] - public void TestCreateCryptoGroupForLevelRngReturnsOdd() + [TestCase(1)] // rng returns even, +1 mod 6 == 1 + [TestCase(4)] // rng returns odd, mod 6 == 1 + [TestCase(6)] // rng returns odd, mod 6 == 5 + [TestCase(8)] // rng returns odd, mod 6 == 3 + public void TestCreateCryptoGroupForLevel(int subtrahend) { int securityLevel = 32; var expectedPrimeLength = MultiplicativeGroupAlgebra.ComputePrimeLengthForSecurityLevel(securityLevel); var expectedOrderLength = NumberLength.FromBitLength(expectedPrimeLength.InBits - 1); - var primeBeforeOrder = BigInteger.Parse("332306998946228968225951765070101393"); + var primeBeforeOrder = BigInteger.Parse("166153499473114484112975882535050653"); // not Sophie Germain prime var order = BigPrime.CreateWithoutChecks(BigInteger.Parse("166153499473114484112975882535050719")); var prime = BigPrime.CreateWithoutChecks(BigInteger.Parse("332306998946228968225951765070101439")); - var rngResponse = (primeBeforeOrder - 2).ToByteArray(); - Debug.Assert(expectedPrimeLength.InBits == NumberLength.GetLength(prime).InBits); Debug.Assert(expectedOrderLength.InBits == NumberLength.GetLength(order).InBits); - var rngMock = new Mock(MockBehavior.Strict); - rngMock.Setup(rng => rng.GetBytes(It.IsAny())) - .Callback(buffer => - { - Buffer.BlockCopy(buffer, 0, rngResponse, 0, Math.Min(rngResponse.Length, buffer.Length)); - } - ); - - var group = MultiplicativeGroupAlgebra.CreateCryptoGroup(securityLevel, rngMock.Object); - Assert.IsInstanceOf(group.Algebra); - - Assert.That(group.SecurityLevel >= securityLevel, "Created group does not meet security level!"); - Assert.AreEqual(order, group.Algebra.Order); - Assert.AreEqual(prime, ((MultiplicativeGroupAlgebra)group.Algebra).Prime); - Assert.That(group.Algebra.IsSafeElement(group.Algebra.Generator)); - } - - - [Test] - public void TestCreateCryptoGroupForLevelRngReturnsEven() - { - int securityLevel = 32; - var expectedPrimeLength = MultiplicativeGroupAlgebra.ComputePrimeLengthForSecurityLevel(securityLevel); - var expectedOrderLength = NumberLength.FromBitLength(expectedPrimeLength.InBits - 1); - - var primeBeforeOrder = BigInteger.Parse("332306998946228968225951765070101393"); - var order = BigPrime.CreateWithoutChecks(BigInteger.Parse("166153499473114484112975882535050719")); - var prime = BigPrime.CreateWithoutChecks(BigInteger.Parse("332306998946228968225951765070101439")); - - var rngResponse = (primeBeforeOrder - 3).ToByteArray(); - - Debug.Assert(expectedPrimeLength.InBits == NumberLength.GetLength(prime).InBits); - Debug.Assert(expectedOrderLength.InBits == NumberLength.GetLength(order).InBits); + var rngResponse = (primeBeforeOrder - subtrahend).ToByteArray(); + Random random = new Random(0); + bool firstCall = true; var rngMock = new Mock(MockBehavior.Strict); rngMock.Setup(rng => rng.GetBytes(It.IsAny())) .Callback(buffer => { - Buffer.BlockCopy(buffer, 0, rngResponse, 0, Math.Min(rngResponse.Length, buffer.Length)); + if (firstCall) + Buffer.BlockCopy(rngResponse, 0, buffer, 0, Math.Min(rngResponse.Length, buffer.Length)); + else + random.NextBytes(buffer); + firstCall = false; } ); diff --git a/CompactCryptoGroupAlgebra/Multiplicative/MultiplicativeGroupAlgebra.cs b/CompactCryptoGroupAlgebra/Multiplicative/MultiplicativeGroupAlgebra.cs index 394d7c0..5be5841 100644 --- a/CompactCryptoGroupAlgebra/Multiplicative/MultiplicativeGroupAlgebra.cs +++ b/CompactCryptoGroupAlgebra/Multiplicative/MultiplicativeGroupAlgebra.cs @@ -1,6 +1,6 @@ // CompactCryptoGroupAlgebra - C# implementation of abelian group algebra for experimental cryptography -// SPDX-FileCopyrightText: 2022 Lukas Prediger +// SPDX-FileCopyrightText: 2022-2024 Lukas Prediger // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileType: SOURCE @@ -213,18 +213,26 @@ public static CryptoGroup CreateCryptoGroup(int security var primeLength = ComputePrimeLengthForSecurityLevel(securityLevel); var sgPrimeLength = NumberLength.FromBitLength(primeLength.InBits - 1); BigInteger sgCandidate = randomNumberGenerator.GetBigIntegerWithLength(sgPrimeLength); - sgCandidate |= BigInteger.One; // ensure sgCandidate is odd + + // Ensure sgCandidate mod 6 == 5. Any prime p can only have p mod 6 == 1 or p mod 6 == 5. + // If sgCandidate mod 6 == 1, then below primeCandidate mod 6 == 3, which makes it divisable by 3. + // If sgCandidate mod 6 == 5, then below primeCandidate mod 6 == 5 as well, meaning it could be prime. + var factorOfSix = sgCandidate / 6; + sgCandidate = factorOfSix * 6 + 5; + BigInteger primeCandidate = 2 * sgCandidate + 1; while ( !PrimalityTest.IsProbablyPrime(sgCandidate, randomNumberGenerator) || !PrimalityTest.IsProbablyPrime(primeCandidate, randomNumberGenerator) ) { - sgCandidate += 2; - primeCandidate += 4; + sgCandidate += 6; + primeCandidate += 12; } + Debug.Assert(NumberLength.GetLength(sgCandidate).InBits == sgPrimeLength.InBits); Debug.Assert(NumberLength.GetLength(primeCandidate).InBits == primeLength.InBits); + Debug.Assert(2 * sgCandidate + 1 == primeCandidate); var groupAlgebra = new MultiplicativeGroupAlgebra( BigPrime.CreateWithoutChecks(primeCandidate), BigPrime.CreateWithoutChecks(sgCandidate), new BigInteger(4) From 383738ef71f13921c09da62fd5d120cc84ad3e0c Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Tue, 15 Oct 2024 22:00:48 +0300 Subject: [PATCH 2/2] Added RandomNumberGeneratorExtensions.GetBigPrime --- .../RandomNumberGeneratorExtensionsTests.cs | 37 ++++++++++++++++-- .../RandomNumberGeneratorExtensions.cs | 39 ++++++++++++++++++- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/CompactCryptoGroupAlgebra.Tests/RandomNumberGeneratorExtensionsTests.cs b/CompactCryptoGroupAlgebra.Tests/RandomNumberGeneratorExtensionsTests.cs index 85792f0..0ed9025 100644 --- a/CompactCryptoGroupAlgebra.Tests/RandomNumberGeneratorExtensionsTests.cs +++ b/CompactCryptoGroupAlgebra.Tests/RandomNumberGeneratorExtensionsTests.cs @@ -1,6 +1,6 @@ // CompactCryptoGroupAlgebra - C# implementation of abelian group algebra for experimental cryptography -// SPDX-FileCopyrightText: 2022 Lukas Prediger +// SPDX-FileCopyrightText: 2022-2024 Lukas Prediger // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileType: SOURCE @@ -85,9 +85,9 @@ public void TestRandomWithLengthNonByteBoundary() var length = NumberLength.FromBitLength(17); var leadingOneRngBuffer = ((BigInteger.One << (3 * 8)) - 1).ToByteArray(); // 3 bytes of ones - var leadingZeroRngBuffer = ((BigInteger.One << (length.InBits - 2))).ToByteArray(); // only bit 16 set + var leadingZeroRngBuffer = (BigInteger.One << (length.InBits - 2)).ToByteArray(); // only bit 16 set var expectedFirst = (BigInteger.One << length.InBits) - 1; - var expectedSecond = ((BigInteger.One << (length.InBits - 2)) ^ (BigInteger.One << (length.InBits - 1))); + var expectedSecond = (BigInteger.One << (length.InBits - 2)) ^ (BigInteger.One << (length.InBits - 1)); bool firstCall = true; var rngMock = new Mock(MockBehavior.Strict); @@ -151,5 +151,36 @@ public void TestRandomWithLengthByteBoundary() rngMock.Verify(rng => rng.GetBytes(It.IsAny()), Times.Exactly(2)); } + + [Test] + [TestCase(3)] // ((prime - 3) | 1) mod 6 == ((prime - 2) | 1) == 5 + [TestCase(4)] // ((prime - 4) | 1) mod 6 == 3 + [TestCase(6)] // ((prime - 6) | 1) mod 6 == 1 + public void TestRandomBigPrime(int subtrahend) + { + var length = NumberLength.FromBitLength(128); + var prime = BigPrime.CreateWithoutChecks(BigInteger.Parse("340282366920938463463374607431768211297")); + + var rngResponse = (prime - subtrahend).ToByteArray(); + + Random random = new Random(0); + bool firstCall = true; + + var rngMock = new Mock(MockBehavior.Strict); + rngMock.Setup(rng => rng.GetBytes(It.IsAny())) + .Callback(buffer => + { + if (firstCall) + Buffer.BlockCopy(rngResponse, 0, buffer, 0, Math.Min(rngResponse.Length, buffer.Length)); + else + random.NextBytes(buffer); + firstCall = false; + }); + + var result = rngMock.Object.GetBigPrime(length); + + Assert.AreEqual(prime, result); + } + } } diff --git a/CompactCryptoGroupAlgebra/RandomNumberGeneratorExtensions.cs b/CompactCryptoGroupAlgebra/RandomNumberGeneratorExtensions.cs index 5b1851d..ea7d13b 100644 --- a/CompactCryptoGroupAlgebra/RandomNumberGeneratorExtensions.cs +++ b/CompactCryptoGroupAlgebra/RandomNumberGeneratorExtensions.cs @@ -1,6 +1,6 @@ // CompactCryptoGroupAlgebra - C# implementation of abelian group algebra for experimental cryptography -// SPDX-FileCopyrightText: 2022 Lukas Prediger +// SPDX-FileCopyrightText: 2022-2024 Lukas Prediger // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileType: SOURCE @@ -59,7 +59,7 @@ public static BigInteger GetBigIntegerBetween( /// Returns a random positive with the given bit length. /// /// Random number generator. - /// The bit length of the generator number. + /// The bit length of the generated number. /// The random . public static BigInteger GetBigIntegerWithLength( this RandomNumberGenerator randomNumberGenerator, NumberLength length @@ -74,5 +74,40 @@ public static BigInteger GetBigIntegerWithLength( candidate |= BigInteger.One << (length.InBits - 1); // ensure msb is set to achieve desired length return candidate; } + + /// + /// Returns a random with the given bit length. + /// + /// Random number generator. + /// The bit length of the generated prime number. + /// The random . + public static BigPrime GetBigPrime( + this RandomNumberGenerator randomNumberGenerator, NumberLength length + ) + { + BigInteger primeCandidate = randomNumberGenerator.GetBigIntegerWithLength(length); + + // Ensure primeCandidate is odd. + primeCandidate |= 1; + + // Ensure primeCandidate mod 6 == 1 or 5. Any prime p can only have p mod 6 == 1 or p mod 6 == 5. + // Relying on bit operations to avoid any branching statements. + var residue = primeCandidate % 6; + primeCandidate += residue & 2; // <-> primeCandidate += (residue == 3) ? 2 : 0; + residue += residue & 2; // <-> residue += (residue == 3) ? 2 : 0; + int step = 2 << (int)((residue ^ (residue >> 2)) & 1); // <-> step = (residue == 1) ? 4 : 2; + int shiftDir = -1 + (step & 2); // <-> shiftDir = (step == 2) ? 1 : -1; + + while (!PrimalityTest.IsProbablyPrime(primeCandidate, randomNumberGenerator)) + { + primeCandidate += step; + + // Below is equivalent to step = (step == 2) ? 4 : 2; + step = (step << shiftDir) | (step >> -shiftDir); + shiftDir = -shiftDir; + } + + return BigPrime.CreateWithoutChecks(primeCandidate); + } } }