Montgomery representation of integer modulo rings gives weird results when cross-compiled with mingw-w64
I use mingw-w64 on Ubuntu 22.04 to cross-compile pbc to get 64-bit Windows binaries since only 32-bit binaries are provided on the homepage.
Build script
set -e
PBC_BUILD_DIR=$(pwd)
GMP_VERSION=6.3.0
PBC_VERSION=0.5.14
apt install mingw-w64 m4 flex bison
wget https://gmplib.org/download/gmp/gmp-${GMP_VERSION}.tar.zst
tar --zstd -xvf gmp-${GMP_VERSION}.tar.zst
rm gmp-${GMP_VERSION}.tar.zst
cd gmp-${GMP_VERSION}
./configure \
CC="x86_64-w64-mingw32-gcc" \
CC_FOR_BUILD="x86_64-linux-gnu-gcc" \
CPPFLAGS="-I${PBC_BUILD_DIR}/include -D__USE_MINGW_ANSI_STDIO=0" \
LDFLAGS="-L${PBC_BUILD_DIR}/lib" \
--host=x86_64-w64-mingw32 \
--prefix=${PBC_BUILD_DIR} \
--enable-static \
--disable-shared
make -j$(nproc)
make check -j$(nproc)
make install
cd ..
wget https://crypto.stanford.edu/pbc/files/pbc-${PBC_VERSION}.tar.gz
tar zxvf pbc-${PBC_VERSION}.tar.gz
rm pbc-${PBC_VERSION}.tar.gz
cd pbc-${PBC_VERSION}
./configure \
CC="x86_64-w64-mingw32-gcc" \
CPPFLAGS="-I${PBC_BUILD_DIR}/include -D__USE_MINGW_ANSI_STDIO=0" \
LDFLAGS="-L${PBC_BUILD_DIR}/lib" \
LIBS="-lgmp" \
--host=x86_64-w64-mingw32 \
--prefix=${PBC_BUILD_DIR} \
--enable-static \
--disable-shared
make -j$(nproc)
make install
cd ..
# ├── build.sh
# ├── main.c
# ├── gmp-x.x.x
# │ └── ...
# ├── include
# │ ├── gmp.h
# │ └── pbc
# │ ├── pbc.h
# │ └── ...
# ├── lib
# │ ├── libgmp.a
# │ ├── libgmp.la
# │ ├── libpbc.a
# │ ├── libpbc.la
# │ └── pkgconfig
# │ └── gmp.pc
# ├── pbc-x.x.x
# │ └── ...
# └── share
# └── ...
# x86_64-w64-mingw32-gcc -Wall -Iinclude -o main main.c -Llib -lpbc -lgmp
Then I found that these binaries always gives weird results. For example, for the following code:
#include "pbc/pbc.h"
// param/a.param
#define CURVE_PARAM ("" \
"type a\n" \
"q 8780710799663312522437781984754049815806883199414208211028653399266475630880222957078625179422662221423155858769582317459277713367317481324925129998224791\n" \
"h 12016012264891146079388821366740534204802954401251311822919615131047207289359704531102844802183906537786776\n" \
"r 730750818665451621361119245571504901405976559617\n" \
"exp2 159\n" \
"exp1 107\n" \
"sign1 1\n" \
"sign0 1\n" \
"")
int main(int argc, char const *argv[]) {
pbc_random_set_deterministic(0x114514);
pairing_t e;
pairing_init_set_str(e, CURVE_PARAM);
element_t x; element_init_Zr(x, e);
element_set0(x);
element_printf("x (set0) = %B\n", x);
element_set1(x);
element_printf("x (set1) = %B\n", x);
element_add(x, x, x);
element_printf("x (add(set1, set1)) = %B\n", x);
element_set_si(x, 0x2);
element_printf("x (set_si 0x2) = %B\n", x);
element_random(x);
element_printf("x (random seed=0x114514) = %B\n", x);
element_from_hash(x, "Test data", 9);
element_printf("x (from_hash \"Test data\") = %B\n", x);
element_clear(x);
pairing_clear(e);
return 0;
}
The output when running on Ubuntu without cross-compiling:
x (set0) = 0
x (set1) = 1
x (add(set1, set1)) = 2
x (set_si 0x2) = 2
x (random seed=0x114514) = 627055856547974011506813785332492935521893207354
x (from_hash "Test data") = 481817657442947330395294957845829757162661110017
Running on Windows with cross-compiled binaries:
x (set0) = 0
x (set1) = 365375757781869456591985165927106086913303379970
x (add(set1, set1)) = 365375757781869456591985165927106086913303379971
x (set_si 0x2) = 365375757781869456591985165927106086913303379971
x (random seed=0x114514) = 704059715193577092360776882805714726779267438415
x (from_hash "Test data") = 269047460191534710553423228971153386492057830638
All of the assignment functions result in errors. The calculator and signature algorithms in the example also don't work. Someone on Stack Overflow seems to have encountered a similar problem.
This problem does not occur if the implementation of integer modulo ring used by pbc is changed to another implementation instead of the default Montgomery representation.
I don't know how to fix the implementation for now. A temporary fix is to add these lines to the code that uses pbc:
#ifdef __MINGW32__
#include "pbc/pbc_fp.h"
#endif
// ...
#ifdef __MINGW32__
pbc_tweak_use_fp("faster");
#endif
... or apply the patch before cross-compiling pbc:
--- arith/fp.c 2013-06-15 11:43:00.000000000 +0800
+++ arith/fp.c 2023-08-07 23:17:39.271450800 +0800
@@ -17,7 +17,11 @@
// By default, use the montfp.c implementation of F_p. After
// pbc_tweak_use_fp(), future field_init_fp calls will use the specified
// implementation. This is useful for benchmarking and testing.
+#ifdef __MINGW32__
+static void (*option_fpinit) (field_ptr f, mpz_t prime) = field_init_faster_fp;
+#else
static void (*option_fpinit) (field_ptr f, mpz_t prime) = field_init_mont_fp;