2020-02-09 21:01:01 +00:00
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# 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
2020-06-14 13:39:06 +00:00
std / [ random , macros , times , strutils ] ,
2020-02-09 21:01:01 +00:00
# Third-party
gmp , stew / byteutils ,
# Internal
2020-02-12 20:57:39 +00:00
.. / constantine / io / io_bigints ,
2020-03-20 22:03:52 +00:00
.. / constantine / arithmetic ,
2020-06-14 13:39:06 +00:00
.. / constantine / primitives ,
.. / constantine / config / [ common , type_bigint ]
2020-02-09 21:01:01 +00:00
2020-06-15 20:58:56 +00:00
echo " \n ------------------------------------------------------ \n "
2020-02-09 21:01:01 +00:00
# We test up to 1024-bit, more is really slow
var bitSizeRNG {. compileTime . } = initRand ( 1234 )
2020-06-14 13:39:06 +00:00
macro testRandomModSizes ( numSizes : static int , rBits , aBits , bBits , body : untyped ) : untyped =
2020-02-09 21:01:01 +00:00
## Generate `numSizes` random bit sizes known at compile-time to test against GMP
## for A mod M
result = newStmtList ( )
for _ in 0 .. < numSizes :
2020-06-14 13:39:06 +00:00
let aBitsVal = bitSizeRNG . rand ( 126 .. 2048 )
let bBitsVal = bitSizeRNG . rand ( 126 .. 2048 )
let rBitsVal = bitSizeRNG . rand ( 62 .. 4096 + 128 )
2020-02-09 21:01:01 +00:00
result . add quote do :
block :
const ` aBits ` = ` aBitsVal `
2020-06-14 13:39:06 +00:00
const ` bBits ` = ` bBitsVal `
const ` rBits ` = ` rBitsVal `
2020-02-09 21:01:01 +00:00
block :
` body `
const # https://gmplib.org/manual/Integer-Import-and-Export.html
2020-06-14 13:39:06 +00:00
GMP_WordLittleEndian {. used . } = - 1 'i32
GMP_WordNativeEndian {. used . } = 0 'i32
GMP_WordBigEndian {. used . } = 1 'i32
2020-02-09 21:01:01 +00:00
GMP_MostSignificantWordFirst = 1 'i32
2020-06-14 13:39:06 +00:00
GMP_LeastSignificantWordFirst {. used . } = - 1 'i32
2020-02-09 21:01:01 +00:00
proc main ( ) =
var gmpRng : gmp_randstate_t
gmp_randinit_mt ( gmpRng )
# The GMP seed varies between run so that
# test coverage increases as the library gets tested.
# This requires to dump the seed in the console or the function inputs
# to be able to reproduce a bug
let seed = uint32 ( getTime ( ) . toUnix ( ) and ( 1 'i64 shl 32 - 1 ) ) # unixTime mod 2^32
echo " GMP seed: " , seed
gmp_randseed_ui ( gmpRng , seed )
2020-06-14 13:39:06 +00:00
var r , a , b : mpz_t
2020-02-09 21:01:01 +00:00
mpz_init ( r )
2020-06-14 13:39:06 +00:00
mpz_init ( a )
mpz_init ( b )
2020-02-09 21:01:01 +00:00
2020-06-14 13:39:06 +00:00
testRandomModSizes ( 128 , rBits , aBits , bBits ) :
2020-02-10 22:56:57 +00:00
# echo "--------------------------------------------------------------------------------"
2020-06-14 13:39:06 +00:00
echo " Testing: random mul r ( " , align ( $ rBits , 4 ) , " -bit) <- a ( " , align ( $ aBits , 4 ) , " -bit) * b ( " , align ( $ bBits , 4 ) , " -bit) (full mul bits: " , align ( $ ( aBits + bBits ) , 4 ) , " ), r large enough? " , rBits > = aBits + bBits
2020-02-09 21:01:01 +00:00
# Generate random value in the range 0 ..< 2^aBits
mpz_urandomb ( a , gmpRng , aBits )
# Generate random modulus and ensure the MSB is set
2020-06-14 13:39:06 +00:00
mpz_urandomb ( b , gmpRng , bBits )
mpz_setbit ( r , aBits + bBits )
2020-02-09 21:01:01 +00:00
2020-02-10 22:56:57 +00:00
# discard gmp_printf(" -- %#Zx mod %#Zx\n", a.addr, m.addr)
2020-02-09 22:26:39 +00:00
2020-02-09 21:01:01 +00:00
#########################################################
# Conversion buffers
const aLen = ( aBits + 7 ) div 8
2020-06-14 13:39:06 +00:00
const bLen = ( bBits + 7 ) div 8
2020-02-09 21:01:01 +00:00
var aBuf : array [ aLen , byte ]
2020-06-14 13:39:06 +00:00
var bBuf : array [ bLen , byte ]
2020-02-09 21:01:01 +00:00
2020-06-14 13:39:06 +00:00
{. push warnings : off . } # deprecated csize
var aW , bW : csize # Word written by GMP
{. pop . }
2020-02-09 21:01:01 +00:00
2020-03-16 15:33:51 +00:00
discard mpz_export ( aBuf [ 0 ] . addr , aW . addr , GMP_MostSignificantWordFirst , 1 , GMP_WordNativeEndian , 0 , a )
2020-06-14 13:39:06 +00:00
discard mpz_export ( bBuf [ 0 ] . addr , bW . addr , GMP_MostSignificantWordFirst , 1 , GMP_WordNativeEndian , 0 , b )
2020-02-09 21:01:01 +00:00
# Since the modulus is using all bits, it's we can test for exact amount copy
2020-03-16 15:33:51 +00:00
doAssert aLen > = aW , " Expected at most " & $ aLen & " bytes but wrote " & $ aW & " for " & toHex ( aBuf ) & " (big-endian) "
2020-06-14 13:39:06 +00:00
doAssert bLen > = bW , " Expected at most " & $ bLen & " bytes but wrote " & $ bW & " for " & toHex ( bBuf ) & " (big-endian) "
2020-02-09 21:01:01 +00:00
# Build the bigint
2020-03-16 15:33:51 +00:00
let aTest = BigInt [ aBits ] . fromRawUint ( aBuf . toOpenArray ( 0 , aW - 1 ) , bigEndian )
2020-06-14 13:39:06 +00:00
let bTest = BigInt [ bBits ] . fromRawUint ( bBuf . toOpenArray ( 0 , bW - 1 ) , bigEndian )
2020-02-09 21:01:01 +00:00
#########################################################
2020-06-14 13:39:06 +00:00
# Multiplication
mpz_mul ( r , a , b )
2020-02-09 21:01:01 +00:00
2020-06-14 13:39:06 +00:00
# If a*b overflow the result size we truncate
const numWords = wordsRequired ( rBits )
when numWords < wordsRequired ( aBits + bBits ) :
echo " truncating from " , wordsRequired ( aBits + bBits ) , " words to " , numWords , " (2^ " , WordBitwidth * numWords , " ) "
r . mpz_tdiv_r_2exp ( r , WordBitwidth * numWords )
# Constantine
var rTest : BigInt [ rBits ]
rTest . prod ( aTest , bTest )
2020-02-09 21:01:01 +00:00
#########################################################
# Check
2020-06-14 13:39:06 +00:00
const rLen = numWords * WordBitWidth
var rGMP : array [ rLen , byte ]
{. push warnings : off . } # deprecated csize
2020-02-09 21:01:01 +00:00
var rW : csize # Word written by GMP
2020-06-14 13:39:06 +00:00
{. pop . }
2020-03-16 15:33:51 +00:00
discard mpz_export ( rGMP [ 0 ] . addr , rW . addr , GMP_MostSignificantWordFirst , 1 , GMP_WordNativeEndian , 0 , r )
2020-02-09 21:01:01 +00:00
2020-06-14 13:39:06 +00:00
var rConstantine : array [ rLen , byte ]
2020-03-16 15:33:51 +00:00
exportRawUint ( rConstantine , rTest , bigEndian )
2020-02-09 21:01:01 +00:00
2020-03-16 15:33:51 +00:00
# Note: in bigEndian, GMP aligns left while constantine aligns right
2020-06-14 13:39:06 +00:00
doAssert rGMP . toOpenArray ( 0 , rW - 1 ) = = rConstantine . toOpenArray ( rLen - rW , rLen - 1 ) , block :
2020-02-10 22:56:57 +00:00
# Reexport as bigEndian for debugging
discard mpz_export ( aBuf [ 0 ] . addr , aW . addr , GMP_MostSignificantWordFirst , 1 , GMP_WordNativeEndian , 0 , a )
2020-06-14 13:39:06 +00:00
discard mpz_export ( bBuf [ 0 ] . addr , bW . addr , GMP_MostSignificantWordFirst , 1 , GMP_WordNativeEndian , 0 , b )
" \n Multiplication with operands \n " &
2020-02-10 22:56:57 +00:00
" a ( " & align ( $ aBits , 4 ) & " -bit): " & aBuf . toHex & " \n " &
2020-06-14 13:39:06 +00:00
" b ( " & align ( $ bBits , 4 ) & " -bit): " & bBuf . toHex & " \n " &
" into r of size " & align ( $ rBits , 4 ) & " -bit failed: " & " \n " &
2020-02-10 22:56:57 +00:00
" GMP: " & rGMP . toHex ( ) & " \n " &
2020-03-16 15:33:51 +00:00
" Constantine: " & rConstantine . toHex ( ) & " \n " &
" (Note that GMP aligns bytes left while constantine aligns bytes right) "
2020-02-09 21:01:01 +00:00
main ( )