Skip to content

Commit

Permalink
Improve the test RNG
Browse files Browse the repository at this point in the history
  • Loading branch information
Coding-Enthusiast committed Dec 24, 2023
1 parent 59e6ccf commit add89e3
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 49 deletions.
12 changes: 6 additions & 6 deletions Src/Tests/Bitcoin/Cryptography/EllipticCurve/Scalar8x32Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -457,12 +457,12 @@ public void GetHashCodeTest()
private const int Count = 64;

// random_scalar_order_test
private static Scalar8x32 CreateRandom(Rand rng)
private static Scalar8x32 CreateRandom(TestRNG rng)
{
byte[] b32 = new byte[32];
do
{
rng.secp256k1_testrand256_test(b32);
rng.Rand256Test(b32);
Scalar8x32 result = new(b32, out bool overflow);
if (!overflow && !result.IsZero)
{
Expand All @@ -472,7 +472,7 @@ private static Scalar8x32 CreateRandom(Rand rng)
}

// scalar_test(void)
private static unsafe void ScalarTest(Rand rng)
private static unsafe void ScalarTest(TestRNG rng)
{
// Set 's' to a random scalar, with value 'snum'.
Scalar8x32 s = CreateRandom(rng);
Expand Down Expand Up @@ -509,7 +509,7 @@ private static unsafe void ScalarTest(Rand rng)
int i = 0;
while (i < 256)
{
int now = (int)(rng.secp256k1_testrand_int(15) + 1);
int now = (int)(rng.RandInt(15) + 1);
if (now + i > 256)
{
now = 256 - i;
Expand All @@ -534,7 +534,7 @@ private static unsafe void ScalarTest(Rand rng)

{
// Test add_bit
uint bit = (uint)rng.secp256k1_testrand_bits(8);
uint bit = (uint)rng.RandBits(8);
Scalar8x32 b = new(1);
Assert.True(b.IsOne);
for (int i = 0; i < bit; i++)
Expand Down Expand Up @@ -623,7 +623,7 @@ private static unsafe void ScalarTest(Rand rng)
[Fact]
public void Libsecp256k1Tests() // run_scalar_tests
{
Rand rng = new();
TestRNG rng = new();
rng.Init(null);

for (int i = 0; i < 128 * Count; i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,27 @@
using Autarkysoft.Bitcoin;
using Autarkysoft.Bitcoin.Cryptography.Hashing;
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;

namespace Tests.Bitcoin.Cryptography.EllipticCurve
{
/// <summary>
/// xoshiro PRNG https://prng.di.unimi.it/
/// https://github.com/bitcoin-core/secp256k1/blob/77af1da9f631fa622fb5b5895fd27be431432368/src/testrand.h
/// https://github.com/bitcoin-core/secp256k1/blob/77af1da9f631fa622fb5b5895fd27be431432368/src/testrand_impl.h
/// A non-cryptographic RNG used only for test infrastructure
/// <para/>xoshiro PRNG https://prng.di.unimi.it/
/// <para/>https://github.com/bitcoin-core/secp256k1/blob/77af1da9f631fa622fb5b5895fd27be431432368/src/testrand.h
/// <para/>https://github.com/bitcoin-core/secp256k1/blob/77af1da9f631fa622fb5b5895fd27be431432368/src/testrand_impl.h
/// </summary>
internal class Rand
public class TestRNG
{
private readonly ulong[] state = new ulong[4];

private void secp256k1_testrand_seed(byte[] seed16)
/// <summary>
/// Seed the pseudorandom number generator for testing
/// </summary>
/// <remarks>secp256k1_testrand_seed</remarks>
private void Seed(byte[] seed16)
{
Assert.Equal(16, seed16.Length);
byte[] PREFIX = Encoding.UTF8.GetBytes("secp256k1 test init");
Expand All @@ -41,65 +47,85 @@ private void secp256k1_testrand_seed(byte[] seed16)
}
}

private static ulong rotl(ulong x, int k) => (x << k) | (x >> (64 - k));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong ROTL(ulong x, int k) => (x << k) | (x >> (64 - k));

private ulong secp256k1_testrand64()
/// <summary>
/// Generate a pseudorandom number in the range [0..2**64-1].
/// </summary>
/// <remarks>secp256k1_testrand64</remarks>
public ulong Rand64()
{
/* Test-only Xoshiro256++ RNG. See https://prng.di.unimi.it/ */
ulong result = rotl(state[0] + state[3], 23) + state[0];
// Test-only Xoshiro256++ RNG. See https://prng.di.unimi.it/
ulong result = ROTL(state[0] + state[3], 23) + state[0];
ulong t = state[1] << 17;
state[2] ^= state[0];
state[3] ^= state[1];
state[1] ^= state[2];
state[0] ^= state[3];
state[2] ^= t;
state[3] = rotl(state[3], 45);
state[3] = ROTL(state[3], 45);
return result;
}

/// <summary>
/// Generate a pseudorandom number in the range [0..2**bits-1].
/// </summary>
/// <remarks>secp256k1_testrand_bits</remarks>
/// <param name="bits">Bits must be 1 or more</param>
/// <returns></returns>
internal ulong secp256k1_testrand_bits(int bits)
public ulong RandBits(int bits)
{
if (bits == 0) return 0;
return secp256k1_testrand64() >> (64 - bits);
return bits == 0 ? 0 : Rand64() >> (64 - bits);
}

uint secp256k1_testrand32()
/// <summary>
/// Generate a pseudorandom number in the range [0..2**32-1]
/// </summary>
/// <remarks>secp256k1_testrand32</remarks>
public uint Rand32()
{
return (uint)(secp256k1_testrand64() >> 32);
return (uint)(Rand64() >> 32);
}

internal uint secp256k1_testrand_int(uint range)
/// <summary>
/// Generate a pseudorandom number in the range [0..range-1]
/// </summary>
/// <remarks>secp256k1_testrand_int</remarks>
public uint RandInt(uint range)
{
uint mask = 0;
uint range_copy;
/* Reduce range by 1, changing its meaning to "maximum value". */
// Reduce range by 1, changing its meaning to "maximum value".
Assert.True(range != 0);
range -= 1;
/* Count the number of bits in range. */
// Count the number of bits in range.
range_copy = range;
while (range_copy != 0)
{
mask = (mask << 1) | 1U;
range_copy >>= 1;
}
/* Generation loop. */
// Generation loop.
while (true)
{
uint val = (uint)(secp256k1_testrand64() & mask);
if (val <= range) return val;
uint val = (uint)(Rand64() & mask);
if (val <= range)
{
return val;
}
}
}

private void secp256k1_testrand256(byte[] b32)
/// <summary>
/// Generate a pseudorandom 32-byte array.
/// </summary>
/// <remarks>secp256k1_testrand256</remarks>
public void Rand256(Span<byte> b32)
{
Assert.True(b32.Length == 32);
for (int i = 0; i < b32.Length; i += 8)
{
ulong val = secp256k1_testrand64();
ulong val = Rand64();
b32[i + 0] = (byte)val;
b32[i + 1] = (byte)(val >> 8);
b32[i + 2] = (byte)(val >> 16);
Expand All @@ -111,7 +137,11 @@ private void secp256k1_testrand256(byte[] b32)
}
}

private void secp256k1_testrand_bytes_test(byte[] bytes, int len)
/// <summary>
/// Generate pseudorandom bytes with long sequences of zero and one bits.
/// </summary>
/// <remarks>secp256k1_testrand_bytes_test</remarks>
public void RandBytesTest(Span<byte> bytes, int len)
{
for (int i = 0; i < len; i++)
{
Expand All @@ -121,8 +151,8 @@ private void secp256k1_testrand_bytes_test(byte[] bytes, int len)
int bits = 0;
while (bits < len * 8)
{
int now = (int)(1 + (secp256k1_testrand_bits(6) * secp256k1_testrand_bits(5) + 16) / 31);
uint val = (uint)secp256k1_testrand_bits(1);
int now = (int)(1 + (RandBits(6) * RandBits(5) + 16) / 31);
uint val = (uint)RandBits(1);
while (now > 0 && bits < len * 8)
{
bytes[bits / 8] |= (byte)(val << (bits % 8));
Expand All @@ -132,27 +162,42 @@ private void secp256k1_testrand_bytes_test(byte[] bytes, int len)
}
}

internal void secp256k1_testrand256_test(byte[] b32)
/// <summary>
/// Generate a pseudorandom 32-byte array with long sequences of zero and one bits.
/// </summary>
/// <remarks>secp256k1_testrand256_test</remarks>
public void Rand256Test(Span<byte> b32)
{
secp256k1_testrand_bytes_test(b32, 32);
Assert.True(b32.Length == 32);
RandBytesTest(b32, 32);
}

internal void secp256k1_testrand256_test(ushort[] arr16)
public void Rand256Test(Span<ushort> arr16)
{
Assert.True(arr16.Length == 16);
byte[] b32 = new byte[32];
secp256k1_testrand_bytes_test(b32, 32);
RandBytesTest(b32, 32);
for (int i = 0, j = 0; i < b32.Length && j < arr16.Length; i += 2, j++)
{
arr16[j] = (ushort)(b32[i] | b32[i + 1] << 8);
}
}

private void secp256k1_testrand_flip(byte[] b, int len)
/// <summary>
/// Flip a single random bit in a byte array
/// </summary>
/// <remarks>secp256k1_testrand_flip</remarks>
public void RandFlip(Span<byte> b, uint len)
{
b[secp256k1_testrand_int((uint)len)] ^= (byte)(1 << (int)secp256k1_testrand_bits(3));
b[(int)RandInt(len)] ^= (byte)(1 << (int)RandBits(3));
}

internal void Init(string hexseed)

/// <summary>
/// Initialize the test RNG using (hex encoded) array up to 16 bytes, or randomly if hexseed is NULL.
/// </summary>
/// <remarks>secp256k1_testrand_init</remarks>
public void Init(string hexseed)
{
byte[] seed16 = new byte[16];
if (!string.IsNullOrEmpty(hexseed))
Expand All @@ -172,17 +217,12 @@ internal void Init(string hexseed)
RandomNumberGenerator.Fill(seed16);
}

secp256k1_testrand_seed(seed16);
Seed(seed16);
}

private void secp256k1_testrand_finish()
{
byte[] run32 = new byte[32];
secp256k1_testrand256(run32);
}


internal void RunXoshiro256ppTests()
public void RunXoshiro256ppTests()
{
// Sanity check that we run before the actual seeding.
for (int i = 0; i < state.Length; i++)
Expand All @@ -198,10 +238,10 @@ internal void RunXoshiro256ppTests()
0x4C, 0xCC, 0xC1, 0x18, 0xB2, 0xD8, 0x8F, 0xEF,
0x43, 0x26, 0x15, 0x57, 0x37, 0x00, 0xEF, 0x30,
};
secp256k1_testrand_seed(seed16);
Seed(seed16);
for (int i = 0; i < 17; i++)
{
secp256k1_testrand256(buf32);
Rand256(buf32);
}
Assert.Equal(buf32_expected, buf32);
}
Expand Down

0 comments on commit add89e3

Please sign in to comment.