> For the complete documentation index, see [llms.txt](https://docs.zama.org/tfhe-rs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zama.org/tfhe-rs/tfhe-rs/0.1-1/shortint/operations.md).

# Operations

## How shortint is represented

In `shortint`, the encrypted data is stored in an LWE ciphertext.

Conceptually, the message stored in an LWE ciphertext is divided into a **carry buffer** and a **message buffer**.

![](https://1972895617-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FdModOfKMxpumfEUTRhEP%2Fuploads%2Fgit-blob-2e16e79986a328dad6229c16ae1a2e04c5126caf%2Fciphertext-representation.png?alt=media)

The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus: the exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.

Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in incorrect outputs.

In order to ensure the correctness of the computation, we keep track of the maximum value encrypted in a ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to safely resume the computations. Therefore, in `shortint` the carry modulus is mainly considered as a means to do more computations.

## Types of operations

The operations available via a `ServerKey` may come in different variants:

* operations that take their inputs as encrypted values.
* scalar operations that take at least one non-encrypted value as input.

For example, the addition has both variants:

* `ServerKey::unchecked_add` which takes two encrypted values and adds them.
* `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the so-called scalar) and adds them.

Each operation may come in different 'flavors':

* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely;
* `smart`: Always does the operation - if the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible.

Not all operations have these 3 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.

## How to use operation types

Let's try to do a circuit evaluation using the different flavours of operations we already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise, the `checked` and `smart` are the best options.

As an example, let's do a scalar multiplication, a subtraction, and a multiplication.

```rust
use tfhe::shortint::prelude::*;


fn main() {
    // We generate a set of client/server keys, using the default parameters:
    let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

    let msg1 = 3;
    let msg2 = 3;
    let scalar = 4;

    let modulus = client_key.parameters.message_modulus.0;

    // We use the client key to encrypt two messages:
    let mut ct_1 = client_key.encrypt(msg1);
    let ct_2 = client_key.encrypt(msg2);

    server_key.unchecked_scalar_mul_assign(&mut ct_1, scalar);
    server_key.unchecked_sub_assign(&mut ct_1, &ct_2);
    server_key.unchecked_mul_lsb_assign(&mut ct_1, &ct_2);

    // We use the client key to decrypt the output of the circuit:
    let output = client_key.decrypt(&ct_1);
    println!("expected {}, found {}", ((msg1 * scalar as u64 - msg2) * msg2) % modulus as u64, output);
}
```

During this computation, the carry buffer has been overflowed and, as all the operations were `unchecked`, the output may be incorrect.

If we redo this same circuit with the `checked` flavour, a panic will occur.

```rust
use tfhe::shortint::prelude::*;

use std::error::Error;

fn main() {
    // We generate a set of client/server keys, using the default parameters:
    let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

    let msg1 = 3;
    let msg2 = 3;
    let scalar = 4;

    let modulus = client_key.parameters.message_modulus.0;

    // We use the client key to encrypt two messages:
    let mut ct_1 = client_key.encrypt(msg1);
    let ct_2 = client_key.encrypt(msg2);

    let mut ops = || -> Result<(), Box<dyn Error>> {
        server_key.checked_scalar_mul_assign(&mut ct_1, scalar)?;
        server_key.checked_sub_assign(&mut ct_1, &ct_2)?;
        server_key.checked_mul_lsb_assign(&mut ct_1, &ct_2)?;
        Ok(())
    };

    match ops() {
        Ok(_) => (),
        Err(e) => {
            println!("correctness of operations is not guaranteed due to error: {}", e);
            return;
        },
    }

    // We use the client key to decrypt the output of the circuit:
    let output = client_key.decrypt(&ct_1);
    assert_eq!(output, ((msg1 * scalar as u64 - msg2) * msg2) % modulus as u64);
}
```

Therefore, the `checked` flavour permits manual management of the overflow of the carry buffer by raising an error if the correctness is not guaranteed.

Lastly, using the `smart` flavour will output the correct result all the time. However, the computation may be slower as the carry buffer may be cleaned during the computations.

```rust
use tfhe::shortint::prelude::*;


fn main() {
    // We generate a set of client/server keys, using the default parameters:
    let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

    let msg1 = 3;
    let msg2 = 3;
    let scalar = 4;

    let modulus = client_key.parameters.message_modulus.0;

    // We use the client key to encrypt two messages:
    let mut ct_1 = client_key.encrypt(msg1);
    let mut ct_2 = client_key.encrypt(msg2);

    server_key.smart_scalar_mul_assign(&mut ct_1, scalar);
    server_key.smart_sub_assign(&mut ct_1, &mut ct_2);
    server_key.smart_mul_lsb_assign(&mut ct_1, &mut ct_2);

    // We use the client key to decrypt the output of the circuit:
    let output = client_key.decrypt(&ct_1);
    assert_eq!(output, ((msg1 * scalar as u64 - msg2) * msg2) % modulus as u64);
}
```

\#List of available operations

{% hint style="warning" %}
Currently, certain operations can only be used if the parameter set chosen is compatible with the bivariate programmable bootstrapping, meaning the carry buffer is larger than or equal to the message buffer. These operations are marked with a star (\*).
{% endhint %}

The list of implemented operations for shortint is:

* addition between two ciphertexts
* addition between a ciphertext and an unencrypted scalar
* comparisons `<`, `<=`, `>`, `>=`, `==` between a ciphertext and an unencrypted scalar
* division of a ciphertext by an unencrypted scalar
* LSB multiplication between two ciphertexts returning the result truncated to fit in the `message buffer`
* multiplication of a ciphertext by an unencrypted scalar
* bitwise shift `<<`, `>>`
* subtraction of a ciphertext by another ciphertext
* subtraction of a ciphertext by an unencrypted scalar
* negation of a ciphertext
* bitwise and, or and xor (\*)
* comparisons `<`, `<=`, `>`, `>=`, `==` between two ciphertexts (\*)
* division between two ciphertexts (\*)
* MSB multiplication between two ciphertexts returning the part overflowing the `message buffer` (\*)

In what follows, some simple code examples are given.

### Public key encryption.

TFHE-rs supports both private and public key encryption methods. Note that the only difference between both lies into the encryption step: in this case, the encryption method is called using `public_key` instead of `client_key`.

Here is a small example on how to use public encryption:

```rust
use tfhe::shortint::prelude::*;

fn main() {
    // Generate the client key and the server key:
    let (cks, _) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
    let pks = PublicKey::new(&cks);

    let msg = 2;
    // Encryption of one message:
    let ct = pks.encrypt(msg);
    // Decryption:
    let dec = cks.decrypt(&ct);
    assert_eq!(dec, msg);
}
```

In what follows, all examples are related to private key encryption.

### Arithmetic operations.

Classical arithmetic operations are supported by shortint:

```rust
use tfhe::shortint::prelude::*;

fn main() {
    // We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
    let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

    let msg1 = 2;
    let msg2 = 1;

    let modulus = client_key.parameters.message_modulus.0;

    // We use the private client key to encrypt two messages:
    let ct_1 = client_key.encrypt(msg1);
    let ct_2 = client_key.encrypt(msg2);

    // We use the server public key to execute an integer circuit:
    let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);

    // We use the client key to decrypt the output of the circuit:
    let output = client_key.decrypt(&ct_3);
    assert_eq!(output, (msg1 + msg2) % modulus as u64);
}
```

#### bitwise operations

Short homomorphic integer types support some bitwise operations.

A simple example on how to use these operations:

```rust
use tfhe::shortint::prelude::*;

fn main() {
    // We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
    let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

    let msg1 = 2;
    let msg2 = 1;

    let modulus = client_key.parameters.message_modulus.0;

    // We use the private client key to encrypt two messages:
    let ct_1 = client_key.encrypt(msg1);
    let ct_2 = client_key.encrypt(msg2);

    // We use the server public key to homomorphically compute a bitwise AND:
    let ct_3 = server_key.unchecked_bitand(&ct_1, &ct_2);

    // We use the client key to decrypt the output of the circuit:
    let output = client_key.decrypt(&ct_3);
    assert_eq!(output, (msg1 & msg2) % modulus as u64);
}
```

#### comparisons

Short homomorphic integer types support comparison operations.

A simple example on how to use these operations:

```rust
use tfhe::shortint::prelude::*;

fn main() {
    // We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
    let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

    let msg1 = 2;
    let msg2 = 1;

    let modulus = client_key.parameters.message_modulus.0;

    // We use the private client key to encrypt two messages:
    let ct_1 = client_key.encrypt(msg1);
    let ct_2 = client_key.encrypt(msg2);

    // We use the server public key to execute an integer circuit:
    let ct_3 = server_key.unchecked_greater_or_equal(&ct_1, &ct_2);

    // We use the client key to decrypt the output of the circuit:
    let output = client_key.decrypt(&ct_3);
    assert_eq!(output, (msg1 >= msg2) as u64 % modulus as u64);
}
```

#### univariate function evaluations

A simple example on how to use this operation to homomorphically compute the hamming weight (i.e., the number of bits equal to one) of an encrypted number.

```rust
use tfhe::shortint::prelude::*;

fn main() {
    // We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
    let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

    let msg1 = 3;

    let modulus = client_key.parameters.message_modulus.0;

    // We use the private client key to encrypt two messages:
    let ct_1 = client_key.encrypt(msg1);

    //define the accumulator as the
    let acc = server_key.generate_accumulator(|n| n.count_ones().into());

    // add the two ciphertexts
    let ct_res = server_key.keyswitch_programmable_bootstrap(&ct_1, &acc);


    // We use the client key to decrypt the output of the circuit:
    let output = client_key.decrypt(&ct_res);
    assert_eq!(output, msg1.count_ones() as u64);
}
```

#### bi-variate function evaluations

Using the shortint types offers the possibility to evaluate bi-variate functions, i.e., functions that takes two ciphertexts as input. This requires choosing a parameter set such that the carry buffer size is at least as large as the message one i.e., PARAM\_MESSAGE\_X\_CARRY\_Y with X <= Y.

Here is a simple code example:

```rust
use tfhe::shortint::prelude::*;

fn main() {
    // We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
    let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

    let msg1 = 3;
    let msg2 = 2;

    let modulus = client_key.parameters.message_modulus.0 as u64;

    // We use the private client key to encrypt two messages:
    let ct_1 = client_key.encrypt(msg1);
    let ct_2 = client_key.encrypt(msg2);

    // Compute the accumulator for the bivariate functions
    let acc = server_key.generate_accumulator_bivariate(|x,y| (x.count_ones()
        + y.count_ones()) as u64 % modulus );

    let ct_res = server_key.keyswitch_programmable_bootstrap_bivariate(&ct_1, &ct_2, &acc);

    // We use the client key to decrypt the output of the circuit:
    let output = client_key.decrypt(&ct_res);
    assert_eq!(output, (msg1.count_ones() as u64 + msg2.count_ones() as u64) % modulus);
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.zama.org/tfhe-rs/tfhe-rs/0.1-1/shortint/operations.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
