Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster prime generation #6

Merged
merged 2 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// CompactCryptoGroupAlgebra - C# implementation of abelian group algebra for experimental cryptography

// SPDX-FileCopyrightText: 2022 Lukas Prediger <lumip@lumip.de>
// SPDX-FileCopyrightText: 2022-2024 Lukas Prediger <lumip@lumip.de>
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileType: SOURCE

Expand Down Expand Up @@ -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<RandomNumberGenerator>(MockBehavior.Strict);
rngMock.Setup(rng => rng.GetBytes(It.IsAny<byte[]>()))
.Callback<byte[]>(buffer =>
{
Buffer.BlockCopy(buffer, 0, rngResponse, 0, Math.Min(rngResponse.Length, buffer.Length));
}
);

var group = MultiplicativeGroupAlgebra.CreateCryptoGroup(securityLevel, rngMock.Object);
Assert.IsInstanceOf<MultiplicativeGroupAlgebra>(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<RandomNumberGenerator>(MockBehavior.Strict);
rngMock.Setup(rng => rng.GetBytes(It.IsAny<byte[]>()))
.Callback<byte[]>(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;
}
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// CompactCryptoGroupAlgebra - C# implementation of abelian group algebra for experimental cryptography

// SPDX-FileCopyrightText: 2022 Lukas Prediger <lumip@lumip.de>
// SPDX-FileCopyrightText: 2022-2024 Lukas Prediger <lumip@lumip.de>
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileType: SOURCE

Expand Down Expand Up @@ -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<RandomNumberGenerator>(MockBehavior.Strict);
Expand Down Expand Up @@ -151,5 +151,36 @@ public void TestRandomWithLengthByteBoundary()

rngMock.Verify(rng => rng.GetBytes(It.IsAny<byte[]>()), 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<RandomNumberGenerator>(MockBehavior.Strict);
rngMock.Setup(rng => rng.GetBytes(It.IsAny<byte[]>()))
.Callback<byte[]>(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);
}

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// CompactCryptoGroupAlgebra - C# implementation of abelian group algebra for experimental cryptography

// SPDX-FileCopyrightText: 2022 Lukas Prediger <lumip@lumip.de>
// SPDX-FileCopyrightText: 2022-2024 Lukas Prediger <lumip@lumip.de>
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileType: SOURCE

Expand Down Expand Up @@ -213,18 +213,26 @@ public static CryptoGroup<BigInteger, BigInteger> 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)
Expand Down
39 changes: 37 additions & 2 deletions CompactCryptoGroupAlgebra/RandomNumberGeneratorExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// CompactCryptoGroupAlgebra - C# implementation of abelian group algebra for experimental cryptography

// SPDX-FileCopyrightText: 2022 Lukas Prediger <lumip@lumip.de>
// SPDX-FileCopyrightText: 2022-2024 Lukas Prediger <lumip@lumip.de>
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileType: SOURCE

Expand Down Expand Up @@ -59,7 +59,7 @@ public static BigInteger GetBigIntegerBetween(
/// Returns a random positive <see cref="BigInteger"/> with the given bit length.
/// </summary>
/// <param name="randomNumberGenerator">Random number generator.</param>
/// <param name="length">The bit length of the generator number.</param>
/// <param name="length">The bit length of the generated number.</param>
/// <returns>The random <see cref="BigInteger"/>.</returns>
public static BigInteger GetBigIntegerWithLength(
this RandomNumberGenerator randomNumberGenerator, NumberLength length
Expand All @@ -74,5 +74,40 @@ public static BigInteger GetBigIntegerWithLength(
candidate |= BigInteger.One << (length.InBits - 1); // ensure msb is set to achieve desired length
return candidate;
}

/// <summary>
/// Returns a random <see cref="BigPrime"/> with the given bit length.
/// </summary>
/// <param name="randomNumberGenerator">Random number generator.</param>
/// <param name="length">The bit length of the generated prime number.</param>
/// <returns>The random <see cref="BigPrime"/>.</returns>
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);
}
}
}