Proving

Now that we know how to construct an FheZkpRuntime, we can use it to instantiate a LinkedProofBuilder:

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);
}
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 mut builder = runtime.linkedproof_builder();
    Ok(())
}

There are a number of methods on the builder that will help you construct the proof you need for your particular protocol. We'll walk through some of the more commonly used methods, but they might not "click" until taking a look at their usage in the private transaction example.

Encrypt

Use the encrypt method if you need to encrypt a value and prove that (1) the ciphertext is freshly encrypted, (2) the ciphertext is well formed, and (3) you know the underlying encrypted message. However, do not use this method if you also need to link the input to a ZKP program.

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);
}
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 mut builder = runtime.linkedproof_builder();
let ct = builder.encrypt(&Signed::from(1), &public_key)?;
    Ok(())
}

Similarly you can call encrypt_symmetric and provide a private key, if you'd like to use a symmetric encryption.

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);
}
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 mut builder = runtime.linkedproof_builder();
let ct = builder.encrypt_symmetric(&Signed::from(1), &private_key)?;
    Ok(())
}

The encrypt_returning_link method returns back a LinkedMessage in addition to the Ciphertext encryption. Use this method if you need to encrypt a value and prove that it is (1) freshly encrypted, (2) well formed, (3) you know the underlying encrypted message, and (4) you want to link the message as an input to a ZKP program.

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);
}
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 mut builder = runtime.linkedproof_builder();
let (ct, link) = builder.encrypt_returning_link(&Signed::from(2), &public_key)?;
let proof = builder
    .zkp_program(app.get_zkp_program(is_greater_than_one).unwrap())?
    .linked_input(link)
    .build();
    Ok(())
}

Again, you can also use encrypt_symmetric_returning_link to do the same thing for a symmetric encryption.

Use the decrypt_returning_link method if you have an existing ciphertext, perhaps the result of some FHE program computation, and you want to prove that (1) the ciphertext is well formed, (2) you know the underlying encrypted message, and (3) you want to link the message as an input to a ZKP program.

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);
}
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 mut builder = runtime.linkedproof_builder();
let existing_ct = runtime.encrypt(Signed::from(1), &public_key)?;
let (pt, link) = builder.decrypt_returning_link::<Signed>(&existing_ct, &private_key)?;
let proof = builder
    .zkp_program(app.get_zkp_program(is_greater_than_one).unwrap())?
    .linked_input(link)
    .build();
    Ok(())
}

Re-encrypt

Lastly, you can use reencrypt to take an existing LinkedMessage and encrypt it again. This might seem strange at first, but you may find cases where this is useful. For example, if you need to encrypt the same value under multiple public keys and you want to show that those ciphertexts are in fact (1) well formed, (2) freshly encrypted, and (3) encrypt the same underlying value, then this method will come in handy. Conveniently, all encryptions of a single link will still be one message to link to the ZKP, so you don't have to provide all of them as separate linked inputs.

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);
}
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 (my_public_key, my_private_key) = runtime.generate_keys()?;
let (other_public_key, other_private_key) = runtime.generate_keys()?;
let mut builder: LinkedProofBuilder = todo!();
let (ct_my_key, link) = builder.encrypt_returning_link(&Signed::from(2), &my_public_key)?;
let ct_other_key = builder.reencrypt(&link, &other_public_key)?;
let proof = builder
    .zkp_program(app.get_zkp_program(is_greater_than_one).unwrap())?
    .linked_input(link)
    .build();
    Ok(())
}

It bears repeating that this method purposefully reveals that two ciphertexts encrypt the same value! So, use this method with care and only when appropriate. There's an example of using this method in the private transaction example.