Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/octra-labs/pvac_hfhe_cpp/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The text utilities provide functions for encrypting and decrypting strings using PVAC-HFHE. These functions handle the packing of byte data into field elements and manage variable-length message encoding.

Functions

enc_text

Encrypts a string into a vector of ciphertexts, packing 15 bytes per ciphertext.
std::vector<Cipher> enc_text(
    const PubKey& pk,
    const SecKey& sk,
    const std::string& msg
)
pk
const PubKey &
required
The public key for encryption
sk
const SecKey &
required
The secret key for encryption
msg
const std::string &
required
The text message to encrypt
return
std::vector<Cipher>
A vector of ciphertexts where:
  • [0] contains the encrypted message length
  • [1...] contain the encrypted message data (15 bytes per ciphertext)

Encoding strategy

  1. Length prefix: The first ciphertext encodes msg.size() as a 64-bit value
  2. Data packing: Each subsequent ciphertext encodes up to 15 bytes using pack_15_bytes_to_fp
  3. Depth hinting: Each chunk uses increasing depth hints (starting at 2) to optimize noise management

Example

PubKey pk = /* ... */;
SecKey sk = /* ... */;

std::string message = "Hello, encrypted world!";
std::vector<Cipher> encrypted = enc_text(pk, sk, message);

// encrypted[0] = Enc(24)  // length
// encrypted[1] = Enc("Hello, encrypte")  // first 15 bytes
// encrypted[2] = Enc("d world!")  // remaining 8 bytes + padding
The depth hint increases with each chunk to manage noise accumulation. This makes later chunks slightly more expensive but maintains decryptability for long messages.

dec_text

Decrypts a vector of ciphertexts back into the original string.
std::string dec_text(
    const PubKey& pk,
    const SecKey& sk,
    const std::vector<Cipher>& cts
)
pk
const PubKey &
required
The public key (used for decryption context)
sk
const SecKey &
required
The secret key for decryption
cts
const std::vector<Cipher> &
required
The encrypted text ciphertexts (must be in the format produced by enc_text)
return
std::string
The decrypted plaintext message. Returns an empty string if cts is empty.

Decoding process

  1. Decrypt the length from cts[0]
  2. Decrypt each chunk from cts[1...] using unpack_fp_to_15_bytes
  3. Concatenate all bytes and truncate to the original length

Error handling

  • If length.hi != 0, a warning is printed to stderr and the value is clipped to 64 bits
  • If the buffer is shorter than the expected length, the result is truncated
std::vector<Cipher> encrypted = enc_text(pk, sk, "Secret message");
std::string recovered = dec_text(pk, sk, encrypted);

assert(recovered == "Secret message");
The decrypted buffer always allocates (length + 16) bytes to handle potential rounding, but only returns the exact length bytes.

pack_15_bytes_to_fp

Packs up to 15 bytes into a single field element.
Fp pack_15_bytes_to_fp(const uint8_t* p, size_t len)
p
const uint8_t *
required
Pointer to the byte array to pack
len
size_t
required
Number of bytes to pack (clamped to maximum of 15)
return
Fp
A field element containing the packed bytes in little-endian order.

Packing layout

Bytes are packed into the 120-bit field element (Fp = 2×64 bits, with 63 bits used in the high word):
Fp.lo  = bytes[0..7]   (little-endian)
Fp.hi  = bytes[8..14]  (little-endian, max 7 bytes)

Capacity

  • Maximum: 15 bytes (120 bits)
  • Field size: 127 bits
  • Safety margin: 7 bits unused
uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
Fp packed = pack_15_bytes_to_fp(data, 5);
// packed.lo = 0x0504030201
// packed.hi = 0x00
Only the first 15 bytes are packed. If len > 15, excess bytes are ignored. Bytes beyond len are treated as zero.

unpack_fp_to_15_bytes

Unpacks a field element into 15 bytes.
void unpack_fp_to_15_bytes(const Fp& x, uint8_t* out)
x
const Fp &
required
The field element to unpack
out
uint8_t *
required
Output buffer (must have space for at least 15 bytes)

Unpacking layout

The inverse of pack_15_bytes_to_fp:
out[0..7]  = Fp.lo  (little-endian)
out[8..14] = Fp.hi  (little-endian)

Buffer requirements

  • Output buffer must be at least 15 bytes
  • No bounds checking is performed
  • Always writes exactly 15 bytes
Fp packed = pack_15_bytes_to_fp((const uint8_t*)"Hello", 5);

uint8_t buffer[15];
unpack_fp_to_15_bytes(packed, buffer);
// buffer[0..4] = "Hello"
// buffer[5..14] = uninitialized/zero
This function always writes 15 bytes. If the original data was shorter, the extra bytes will contain the padding (zeros) from the packing operation.

Usage patterns

Basic text encryption

// Encrypt
std::string plaintext = "Confidential data";
std::vector<Cipher> encrypted = enc_text(pk, sk, plaintext);

// Decrypt
std::string recovered = dec_text(pk, sk, encrypted);
assert(recovered == plaintext);

Custom data encoding

For non-text binary data, you can use the packing functions directly:
// Encrypt binary data in chunks
std::vector<uint8_t> data = /* ... */;
std::vector<Cipher> chunks;

for (size_t i = 0; i < data.size(); i += 15) {
    size_t chunk_size = std::min((size_t)15, data.size() - i);
    Fp packed = pack_15_bytes_to_fp(&data[i], chunk_size);
    chunks.push_back(enc_fp(pk, sk, packed));
}

Length limits

  • Maximum message length: 2^64 - 1 bytes (practically unlimited)
  • Ciphertext expansion: ceil(message_length / 15) + 1 ciphertexts
  • Each ciphertext adds ~150-300 KB depending on noise levels

Performance considerations

Ciphertext size

For a message of length L:
Ciphertexts = 1 + ceil(L / 15)
Total size ≈ (1 + ceil(L / 15)) × (edges × 150 bytes)
Example: A 150-byte message requires ~11 ciphertexts.

Depth management

The increasing depth hint strategy means:
  • First chunks encrypt faster (depth 2)
  • Later chunks are slower but maintain correctness
  • For very long messages (>1 KB), consider batching or compression

Source location

include/pvac/utils/text.hpp

Build docs developers (and LLMs) love