# \[Bug report\] \`ConvertToInt32\` silently narrows centered values outside the int32\_t range

**URL:** <https://openfhe.discourse.group/t/bug-report-converttoint32-silently-narrows-centered-values-outside-the-int32-t-range/2360>\
**Category:** Bug Reports\
**Created:** [August 5, 2026, 10:30am UTC](https://openfhe.discourse.group/t/bug-report-converttoint32-silently-narrows-centered-values-outside-the-int32-t-range/2360 "2026-08-05T10:30:40Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![CCYJ](https://avatars.discourse-cdn.com/v4/letter/c/b5ac83/32.png) [@CCYJ](https://openfhe.discourse.group/u/CCYJ)\
**Post date:** [August 5, 2026, 10:30am UTC](https://openfhe.discourse.group/t/bug-report-converttoint32-silently-narrows-centered-values-outside-the-int32-t-range/2360/1 "2026-08-05T10:30:40Z")

</div>

Hi OpenFHE team,  
I would like to report a reproducible correctness bug in OpenFHE’s public matrix conversion API:

```cpp
Matrix<int32_t> ConvertToInt32(const Matrix<BigInteger>& input,
                               const BigInteger& modulus);

```

The function is documented as converting values from `Z_q` to the centered range `[-q/2, q/2]`, but it can return a negative `int32_t` for a residue whose mathematical centered representative is positive but lies outside the `int32_t` range.

## Summary

The public declarations are:

- `src/core/include/math/matrix.h`: lines 785-795

The implementation uses `ConvertToInt()` and assigns directly into `int32_t`:

```cpp
result(i, j) = input(i, j).ConvertToInt();

```

and similarly for `Matrix<BigVector>`.

On the tested backend configuration (`NATIVE_SIZE=64`, `MATHBACKEND=4`), `BasicInteger` is `uint64_t`, so `ConvertToInt()` returns `uint64_t`.

If:

```plaintext
INT32_MAX < value <= floor(modulus / 2)

```

the value is still positive in centered form, but the assignment to `int32_t` can produce an implementation-defined narrowing result. With Clang 14 on the tested x86-64 configuration, `2147483648` becomes `-2147483648`.

I reproduced this on:

- tested revision: `ed361af22049007db2107e7c69bcff209e8c420d`

The affected source file matches the `v1.5.1` release for this code path.

The corresponding `MatrixStrassen` conversion helpers contain the same unchecked narrowing pattern. I have not separately reproduced those overloads in this report.

## Environment

- OpenFHE tested revision: `ed361af22049007db2107e7c69bcff209e8c420d`
- Matching public release for this code path: `v1.5.1`
- Backend configuration: `MATHBACKEND=4`, `NATIVE_SIZE=64`
- Library configuration: `BUILD_STATIC=ON`, `BUILD_SHARED=OFF`
- OS: Linux x86\_64
- Compiler: Clang `14.0.0`

## Minimal reproduction

The following program uses the public `ConvertToInt32(...)` API with:

- modulus `q = 4294967311`
- matrix entry `a = 2147483648 = 2^31`

Since:

```plaintext
floor(q / 2) = 2147483655

```

the mathematical centered representative is still positive.

```cpp
#include "math/matrix.h"
#include "openfhecore.h"

#include <cstdint>
#include <iostream>

using namespace lbcrypto;

int main() {
    const BigInteger modulus("4294967311");
    const BigInteger coeff("2147483648");

    Matrix<BigInteger> input([]() { return BigInteger(0); }, 1, 1);
    input(0, 0) = coeff;

    Matrix<int32_t> output = ConvertToInt32(input, modulus);
    const int64_t mathematicalCenteredValue = 2147483648LL;
    const int64_t observed = static_cast<int64_t>(output(0, 0));

    std::cout << "modulus=" << modulus << std::endl;
    std::cout << "coeff=" << coeff << std::endl;
    std::cout << "threshold=" << (modulus / BigInteger(2)) << std::endl;
    std::cout << "observed_int32=" << observed << std::endl;
    std::cout << "mathematical_centered_value="
              << mathematicalCenteredValue << std::endl;
    std::cout << "sign_corrupted=" << (observed < 0 ? 1 : 0) << std::endl;
    return observed < 0 ? 1 : 0;
}

```

A minimal `CMakeLists.txt` used for this probe is:

```cmake
cmake_minimum_required(VERSION 3.16)
project(openfhe_matrix_converttoint32_probe CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(OPENFHE_SRC_DIR "/path/to/openfhe-development")
set(OPENFHE_BUILD_DIR "/path/to/openfhe-build")

add_executable(openfhe_matrix_converttoint32_probe
    openfhe_matrix_converttoint32_probe.cpp)

target_include_directories(openfhe_matrix_converttoint32_probe PRIVATE
    "${OPENFHE_SRC_DIR}/third-party/include"
    "${OPENFHE_SRC_DIR}/third-party/cereal/include"
    "${OPENFHE_SRC_DIR}/src/core/include"
    "${OPENFHE_BUILD_DIR}/src/core"
    "${OPENFHE_SRC_DIR}/src/core/lib")

target_link_libraries(openfhe_matrix_converttoint32_probe PRIVATE
    "${OPENFHE_BUILD_DIR}/lib/libOPENFHEcore_static.a"
    pthread
    dl
    m)

```

The OpenFHE library build used by this probe was configured with:

```bash
cmake -S /path/to/openfhe-development \
  -B /path/to/openfhe-build \
  -DCMAKE_CXX_COMPILER=clang++-14 \
  -DBUILD_STATIC=ON \
  -DBUILD_SHARED=OFF \
  -DMATHBACKEND=4 \
  -DNATIVE_SIZE=64

```

Representative build steps:

```bash
cmake -S . -B build \
  -DCMAKE_CXX_COMPILER=clang++-14

cmake --build build -j

./build/openfhe_matrix_converttoint32_probe

```

## Actual behavior

The probe returns failure and prints:

```plaintext
modulus=4294967311
coeff=2147483648
threshold=2147483655
observed_int32=-2147483648
mathematical_centered_value=2147483648
sign_corrupted=1

```

So the mathematical centered representative is positive, but the API returns a negative `int32_t`.

## Expected behavior

The mathematical centered representative here is `+2147483648`, which is not representable as `int32_t`.

Therefore, the current API cannot return the correct value for this input. It should either:

- reject the conversion before narrowing, with a documented representability  
precondition; or
- provide an API returning a wider signed type.

## Cause analysis

The public declarations are:

- `src/core/include/math/matrix.h`: lines 785-795

The implementation is:

```cpp
Matrix<int32_t> ConvertToInt32(const Matrix<BigInteger>& input,
                               const BigInteger& modulus) {
    ...
    BigInteger negativeThreshold(modulus / BigInteger(2));
    ...
    if (input(i, j) > negativeThreshold) {
        result(i, j) = -1 * (modulus - input(i, j)).ConvertToInt();
    }
    else {
        result(i, j) = input(i, j).ConvertToInt();
    }
}

```

In the tested backend configuration (`NATIVE_SIZE=64`, `MATHBACKEND=4`), `BasicInteger` is `uint64_t`, and `ConvertToInt()` defaults to that backend type:

```cpp
template <typename T = BasicInteger>
T ConvertToInt() const noexcept

```

So the positive branch effectively does:

```plaintext
BigInteger(2147483648)
  -> uint64_t(2147483648)
  -> unchecked conversion to int32_t
  -> implementation-defined result

```

without checking whether the centered value actually fits in signed 32-bit range.

## Impact

This is a public result-integrity bug in a documented conversion helper.

It affects callers that pass residues whose mathematical centered representatives fall outside the `int32_t` range. The public API neither documents such inputs as invalid nor reports that the result is unrepresentable.

The confirmed impact on the tested configuration is:

- silent sign corruption in the returned `Matrix<int32_t>`

I have not yet traced a higher-level cryptographic failure from this helper in this report, but this conversion API is part of the public math/lattice layer and can taint any downstream workflow that assumes the centered conversion is correct.

## Relevant source locations

- Public declarations:  
[https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/include/math/matrix.h#L785-L795](https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/include/math/matrix.h#L785-L795)
- `Matrix<BigInteger>` and `Matrix<BigVector>` implementations:  
[https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/lib/math/matrix.cpp#L154-L189](https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/lib/math/matrix.cpp#L154-L189)
- Default `BasicInteger = uint64_t` on the tested configuration:  
[https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/include/math/hal/basicint.h#L46-L58](https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/include/math/hal/basicint.h#L46-L58)
- Default bigint `ConvertToInt()` template:  
[https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/include/math/hal/bigintdyn/ubintdyn.h#L715-L727](https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/include/math/hal/bigintdyn/ubintdyn.h#L715-L727)
- `MatrixStrassen` helpers with the same unchecked narrowing pattern:  
[https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/include/math/matrixstrassen-impl.h#L520-L551](https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/core/include/math/matrixstrassen-impl.h#L520-L551)

## Suggested direction

If the intended contract is truly “convert centered values to `int32_t`”, the implementation should perform the representability check while the value, or its negative-side magnitude, is still represented as `BigInteger`, before calling `ConvertToInt()`.

Only after proving that the mathematical centered representative lies in the range:

```plaintext
INT32_MIN <= centered <= INT32_MAX

```

should the implementation convert it to a fixed-width integer and assign it to the result matrix.

The negative boundary also requires special handling: a magnitude of `2^31` is valid because it represents `INT32_MIN`, even though positive `2^31` is not representable as `int32_t`.

Concretely, that means:

- positive branch: require `value <= INT32_MAX`
- negative branch: let `magnitude = modulus - value`, then require `magnitude <= 2^31`
- if `magnitude == 2^31`, map that case specifically to `INT32_MIN`

If larger centered values are legitimate inputs, the API should return a wider type instead of silently narrowing into `int32_t`.

The same cleanup should also remove the current pattern of performing signed-valued centered arithmetic via unsigned `ConvertToInt()` results in the negative branch.

Reported by Jiang Chao, Beijing University of Posts and Telecommunications

---

<div class="post-metadata">

**Author:** ![dsuponitskiy-duality](https://avatars.discourse-cdn.com/v4/letter/d/c68b51/32.png) [@dsuponitskiy-duality](https://openfhe.discourse.group/u/dsuponitskiy-duality)\
**Post date:** [August 11, 2026, 3:46pm UTC](https://openfhe.discourse.group/t/bug-report-converttoint32-silently-narrows-centered-values-outside-the-int32-t-range/2360/2 "2026-08-11T15:46:11Z")

</div>

Thank you for reporting it. Added [a new issue.](https://github.com/openfheorg/openfhe-development/issues/1249)
