From add89e3fbc239676c74f18d8d70157d4e82e0dcc Mon Sep 17 00:00:00 2001 From: CodingEnthusiast Date: Sun, 24 Dec 2023 06:32:21 +0330 Subject: [PATCH] Improve the test RNG --- .../EllipticCurve/Scalar8x32Tests.cs | 12 +- .../EllipticCurve/{Rand.cs => TestRNG.cs} | 126 ++++++++++++------ 2 files changed, 89 insertions(+), 49 deletions(-) rename Src/Tests/Bitcoin/Cryptography/EllipticCurve/{Rand.cs => TestRNG.cs} (54%) diff --git a/Src/Tests/Bitcoin/Cryptography/EllipticCurve/Scalar8x32Tests.cs b/Src/Tests/Bitcoin/Cryptography/EllipticCurve/Scalar8x32Tests.cs index 6a37ce3..d54fb4b 100644 --- a/Src/Tests/Bitcoin/Cryptography/EllipticCurve/Scalar8x32Tests.cs +++ b/Src/Tests/Bitcoin/Cryptography/EllipticCurve/Scalar8x32Tests.cs @@ -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) { @@ -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); @@ -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; @@ -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++) @@ -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++) diff --git a/Src/Tests/Bitcoin/Cryptography/EllipticCurve/Rand.cs b/Src/Tests/Bitcoin/Cryptography/EllipticCurve/TestRNG.cs similarity index 54% rename from Src/Tests/Bitcoin/Cryptography/EllipticCurve/Rand.cs rename to Src/Tests/Bitcoin/Cryptography/EllipticCurve/TestRNG.cs index 39f2943..71e69f7 100644 --- a/Src/Tests/Bitcoin/Cryptography/EllipticCurve/Rand.cs +++ b/Src/Tests/Bitcoin/Cryptography/EllipticCurve/TestRNG.cs @@ -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 { /// - /// 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 + /// 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 /// - internal class Rand + public class TestRNG { private readonly ulong[] state = new ulong[4]; - private void secp256k1_testrand_seed(byte[] seed16) + /// + /// Seed the pseudorandom number generator for testing + /// + /// secp256k1_testrand_seed + private void Seed(byte[] seed16) { Assert.Equal(16, seed16.Length); byte[] PREFIX = Encoding.UTF8.GetBytes("secp256k1 test init"); @@ -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() + /// + /// Generate a pseudorandom number in the range [0..2**64-1]. + /// + /// secp256k1_testrand64 + 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; } /// /// Generate a pseudorandom number in the range [0..2**bits-1]. /// + /// secp256k1_testrand_bits /// Bits must be 1 or more - /// - 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() + /// + /// Generate a pseudorandom number in the range [0..2**32-1] + /// + /// secp256k1_testrand32 + public uint Rand32() { - return (uint)(secp256k1_testrand64() >> 32); + return (uint)(Rand64() >> 32); } - internal uint secp256k1_testrand_int(uint range) + /// + /// Generate a pseudorandom number in the range [0..range-1] + /// + /// secp256k1_testrand_int + 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) + /// + /// Generate a pseudorandom 32-byte array. + /// + /// secp256k1_testrand256 + public void Rand256(Span 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); @@ -111,7 +137,11 @@ private void secp256k1_testrand256(byte[] b32) } } - private void secp256k1_testrand_bytes_test(byte[] bytes, int len) + /// + /// Generate pseudorandom bytes with long sequences of zero and one bits. + /// + /// secp256k1_testrand_bytes_test + public void RandBytesTest(Span bytes, int len) { for (int i = 0; i < len; i++) { @@ -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)); @@ -132,27 +162,42 @@ private void secp256k1_testrand_bytes_test(byte[] bytes, int len) } } - internal void secp256k1_testrand256_test(byte[] b32) + /// + /// Generate a pseudorandom 32-byte array with long sequences of zero and one bits. + /// + /// secp256k1_testrand256_test + public void Rand256Test(Span 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 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) + /// + /// Flip a single random bit in a byte array + /// + /// secp256k1_testrand_flip + public void RandFlip(Span 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) + + /// + /// Initialize the test RNG using (hex encoded) array up to 16 bytes, or randomly if hexseed is NULL. + /// + /// secp256k1_testrand_init + public void Init(string hexseed) { byte[] seed16 = new byte[16]; if (!string.IsNullOrEmpty(hexseed)) @@ -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++) @@ -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); }