Custom bounds

If you are comfortable with the math behind the SDLP, advanced users may wish to customize certain bounds in the secret S. Note that the correctness of linking types relies on the bounds we've used in our implementation (which varies among the FHE types but generally looks like a bound up to the plaintext modulus for coefficients under degree 256, and zero for greater coefficients). For this reason, we expressly discourage changing the bounds for any messages that are linked to ZKP programs. However, you may wish to change the bound on noise terms for computed ciphertexts; we use a liberal bound of $\Delta/2$ for each coefficient in the noise polynomial, which is the maximum noise permitted for a valid decryption. If you want to ensure that a computed ciphertext has much less noise, perhaps to use it as an input for further computation, you can lower this bound.

To do this, first familiarize yourself with the documentation concerning the shape of S. Then you can modify its bounds with the code below.

use sunscreen::{
    bulletproofs::BulletproofsBackend,
    fhe_program,
    linked::{LinkedProof, LinkedProofBuilder},
    types::{
        bfv::Signed,
        zkp::{
            AsFieldElement, BfvSigned, BulletproofsField, ConstrainCmp, ConstrainFresh, Field,
            FieldSpec,
        },
        Cipher,
    },
    zkp_program, zkp_var, Ciphertext, CompiledFheProgram, CompiledZkpProgram, Compiler, Error,
    FheProgramInput, FheRuntime, FheZkpApplication, FheZkpRuntime, Params, PrivateKey, PublicKey,
    Result, ZkpProgramInput,
};

#[fhe_program(scheme = "bfv")]
fn increase_by_factor(x: Signed, scale: Cipher<Signed>) -> Cipher<Signed> {
    x * scale
}

#[zkp_program]
fn is_greater_than_one<F: FieldSpec>(#[linked] scale: BfvSigned<F>) {
    scale
        .into_field_elem()
        .constrain_gt_bounded(zkp_var!(1), 64);
}
use sunscreen::linked::Bounds;

fn main() -> Result<(), Error> {
let app = Compiler::new()
    .fhe_program(increase_by_factor)
    .zkp_backend::<BulletproofsBackend>()
    .zkp_program(is_greater_than_one)
    .compile()?;
let runtime = FheZkpRuntime::new(app.params(), &BulletproofsBackend::new())?;
let (public_key, private_key) = runtime.generate_keys()?;
let existing_ct = runtime.encrypt(Signed::from(2), &public_key)?;

let mut proof_builder = runtime.linkedproof_builder();

// Assume existing ciphertext comes out of a computation
let (pt, link) = proof_builder.decrypt_returning_link::<Signed>(&existing_ct, &private_key)?;

// For a single decryption statement, S will have one column and four rows, with
// the last entry containing the noise. Let's lower the bound on each
// coefficient in the noise polynomial to 32 bits.
let degree = app.params().lattice_dimension as usize;
let proof = proof_builder
    .add_custom_bounds(3, 0, Bounds(vec![32; degree]))
    .zkp_program(app.get_zkp_program(is_greater_than_one).unwrap())?
    .linked_input(link)
    .build()?;

let mut verify_builder = runtime.linkedproof_verification_builder();
verify_builder.decrypt_returning_link::<Signed>(&existing_ct)?;
// The verifier must specify the same bounds!
verify_builder
    .add_custom_bounds(3, 0, Bounds(vec![32; degree]))
    .proof(proof)
    .zkp_program(app.get_zkp_program(is_greater_than_one).unwrap())?
    .verify()?;
    Ok(())
}