197 lines
6.3 KiB
Nim
197 lines
6.3 KiB
Nim
# Stint
|
|
# Copyright (c) 2018-2022 Status Research & Development GmbH
|
|
#
|
|
# Licensed and distributed under either of
|
|
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
|
|
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
import
|
|
# Standard library
|
|
std/[unittest, times, strutils],
|
|
# Third-party
|
|
gmp, stew/byteutils,
|
|
# Internal
|
|
../stint,
|
|
# Test utilities
|
|
../helpers/[prng_unsafe, staticfor]
|
|
|
|
const
|
|
Iters = 1000
|
|
Bitwidths = [128, 256, 512, 1024, 2048]
|
|
|
|
const # https://gmplib.org/manual/Integer-Import-and-Export.html
|
|
GMP_WordLittleEndian {.used.} = -1'i32
|
|
GMP_WordNativeEndian {.used.} = 0'i32
|
|
GMP_WordBigEndian {.used.} = 1'i32
|
|
|
|
GMP_MostSignificantWordFirst = 1'i32
|
|
GMP_LeastSignificantWordFirst {.used.} = -1'i32
|
|
|
|
var rng: RngState
|
|
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
|
|
rng.seed(seed)
|
|
echo "\n------------------------------------------------------\n"
|
|
echo "t_randomized_vs_gmp xoshiro512** seed: ", seed
|
|
|
|
proc rawUint(dst: var openArray[byte], src: mpz_t): csize =
|
|
## Converts a GMP bigint to a canonical integer as a BigEndian array of byte
|
|
## Returns the number of words actually written
|
|
discard mpz_export(dst[0].addr, result.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, src)
|
|
|
|
proc fromStuint[bits: static int](dst: var mpz_t, src: Stuint[bits]) =
|
|
let t = src.toBytes()
|
|
mpz_import(dst, t.len, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, t[0].addr)
|
|
|
|
# Sanity check
|
|
var t2: typeof(t)
|
|
let wordsWritten = t2.rawUint(dst)
|
|
# Note: in bigEndian, GMP aligns left while Stint aligns right
|
|
doAssert t2.toOpenArray(0, wordsWritten-1) == t.toOpenArray(t.len-wordsWritten, t.len-1)
|
|
|
|
proc test_add(bits: static int, iters: int, gen: RandomGen) =
|
|
|
|
const N = (bits + 7) div 8
|
|
|
|
var x, y, z, m: mpz_t
|
|
mpz_init(x)
|
|
mpz_init(y)
|
|
mpz_init(z)
|
|
mpz_init(m)
|
|
mpz_ui_pow_ui(m, 2, bits) # 2^bits
|
|
|
|
for _ in 0 ..< iters:
|
|
let a = rng.random_elem(Stuint[bits], gen)
|
|
let b = rng.random_elem(Stuint[bits], gen)
|
|
|
|
x.fromStuint(a)
|
|
y.fromStuint(b)
|
|
|
|
let c = a + b
|
|
mpz_add(z, x, y)
|
|
mpz_mod(z, z, m)
|
|
|
|
let cBytes = c.toBytes()
|
|
|
|
var zBytes: array[N, byte]
|
|
let wordsWritten = zBytes.rawUint(z)
|
|
|
|
# Note: in bigEndian, GMP aligns left while Stint aligns right
|
|
doAssert zBytes.toOpenArray(0, wordsWritten-1) == cBytes.toOpenArray(N-wordsWritten, N-1), block:
|
|
# Reexport as bigEndian for debugging
|
|
var xBuf, yBuf: array[N, byte]
|
|
discard xBuf.rawUint(x)
|
|
discard yBuf.rawUint(y)
|
|
"\nAddition with operands\n" &
|
|
" x (" & align($bits, 4) & "-bit): 0x" & xBuf.toHex & "\n" &
|
|
" y (" & align($bits, 4) & "-bit): 0x" & yBuf.toHex & "\n" &
|
|
"failed:" & "\n" &
|
|
" GMP: 0x" & zBytes.toHex() & "\n" &
|
|
" Stint: 0x" & cBytes.toHex() & "\n" &
|
|
"(Note that GMP aligns bytes left while Stint aligns bytes right)"
|
|
|
|
template testAddition(bits: static int) =
|
|
test "Addition vs GMP (" & $bits & " bits)":
|
|
test_add(bits, Iters, Uniform)
|
|
test_add(bits, Iters, HighHammingWeight)
|
|
test_add(bits, Iters, Long01Sequence)
|
|
|
|
proc test_sub(bits: static int, iters: int, gen: RandomGen) =
|
|
|
|
const N = (bits + 7) div 8
|
|
|
|
var x, y, z, m: mpz_t
|
|
mpz_init(x)
|
|
mpz_init(y)
|
|
mpz_init(z)
|
|
mpz_init(m)
|
|
mpz_ui_pow_ui(m, 2, bits) # 2^bits
|
|
|
|
for _ in 0 ..< iters:
|
|
let a = rng.random_elem(Stuint[bits], gen)
|
|
let b = rng.random_elem(Stuint[bits], gen)
|
|
|
|
x.fromStuint(a)
|
|
y.fromStuint(b)
|
|
|
|
let c = a - b
|
|
mpz_sub(z, x, y)
|
|
mpz_mod(z, z, m)
|
|
|
|
let cBytes = c.toBytes()
|
|
|
|
var zBytes: array[N, byte]
|
|
let wordsWritten = zBytes.rawUint(z)
|
|
|
|
# Note: in bigEndian, GMP aligns left while Stint aligns right
|
|
doAssert zBytes.toOpenArray(0, wordsWritten-1) == cBytes.toOpenArray(N-wordsWritten, N-1), block:
|
|
# Reexport as bigEndian for debugging
|
|
var xBuf, yBuf: array[N, byte]
|
|
discard xBuf.rawUint(x)
|
|
discard yBuf.rawUint(y)
|
|
"\nSubstraction with operands\n" &
|
|
" x (" & align($bits, 4) & "-bit): 0x" & xBuf.toHex & "\n" &
|
|
" y (" & align($bits, 4) & "-bit): 0x" & yBuf.toHex & "\n" &
|
|
"failed:" & "\n" &
|
|
" GMP: 0x" & zBytes.toHex() & "\n" &
|
|
" Stint: 0x" & cBytes.toHex() & "\n" &
|
|
"(Note that GMP aligns bytes left while Stint aligns bytes right)"
|
|
|
|
template testSubstraction(bits: static int) =
|
|
test "Substaction vs GMP (" & $bits & " bits)":
|
|
test_sub(bits, Iters, Uniform)
|
|
test_sub(bits, Iters, HighHammingWeight)
|
|
test_sub(bits, Iters, Long01Sequence)
|
|
|
|
proc test_mul(bits: static int, iters: int, gen: RandomGen) =
|
|
|
|
const N = (bits + 7) div 8
|
|
|
|
var x, y, z, m: mpz_t
|
|
mpz_init(x)
|
|
mpz_init(y)
|
|
mpz_init(z)
|
|
mpz_init(m)
|
|
mpz_ui_pow_ui(m, 2, bits) # 2^bits
|
|
|
|
for _ in 0 ..< iters:
|
|
let a = rng.random_elem(Stuint[bits], gen)
|
|
let b = rng.random_elem(Stuint[bits], gen)
|
|
|
|
x.fromStuint(a)
|
|
y.fromStuint(b)
|
|
|
|
let c = a * b
|
|
mpz_mul(z, x, y)
|
|
mpz_mod(z, z, m)
|
|
|
|
let cBytes = c.toBytes()
|
|
|
|
var zBytes: array[N, byte]
|
|
let wordsWritten = zBytes.rawUint(z)
|
|
|
|
# Note: in bigEndian, GMP aligns left while Stint aligns right
|
|
doAssert zBytes.toOpenArray(0, wordsWritten-1) == cBytes.toOpenArray(N-wordsWritten, N-1), block:
|
|
# Reexport as bigEndian for debugging
|
|
var xBuf, yBuf: array[N, byte]
|
|
discard xBuf.rawUint(x)
|
|
discard yBuf.rawUint(y)
|
|
"\nMultiplication with operands\n" &
|
|
" x (" & align($bits, 4) & "-bit): 0x" & xBuf.toHex & "\n" &
|
|
" y (" & align($bits, 4) & "-bit): 0x" & yBuf.toHex & "\n" &
|
|
"failed:" & "\n" &
|
|
" GMP: 0x" & zBytes.toHex() & "\n" &
|
|
" Stint: 0x" & cBytes.toHex() & "\n" &
|
|
"(Note that GMP aligns bytes left while Stint aligns bytes right)"
|
|
|
|
template testMultiplication(bits: static int) =
|
|
test "Multiplication vs GMP (" & $bits & " bits)":
|
|
test_mul(bits, Iters, Uniform)
|
|
test_mul(bits, Iters, HighHammingWeight)
|
|
test_mul(bits, Iters, Long01Sequence)
|
|
|
|
suite "Randomized arithmetic tests vs GMP":
|
|
staticFor i, 0, Bitwidths.len:
|
|
testAddition(Bitwidths[i])
|
|
testSubstraction(Bitwidths[i])
|
|
testMultiplication(Bitwidths[i]) |