mirror of
https://github.com/Sunscreen-tech/Sunscreen.git
synced 2026-04-19 03:00:06 -04:00
Update docs
Remove the low level math explanations from linked proof API docs, those are better suited for user docs. Code example lives instead in./examples, like others
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -2181,6 +2181,13 @@ dependencies = [
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "private_tx_linkedproof"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sunscreen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
|
||||
7
examples/private_tx_linkedproof/Cargo.toml
Normal file
7
examples/private_tx_linkedproof/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "private_tx_linkedproof"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
sunscreen = { workspace = true, features = ["linkedproofs"] }
|
||||
78
examples/private_tx_linkedproof/src/main.rs
Normal file
78
examples/private_tx_linkedproof/src/main.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use sunscreen::{
|
||||
bulletproofs::BulletproofsBackend,
|
||||
fhe_program,
|
||||
linked::LogProofBuilder,
|
||||
types::{
|
||||
bfv::Signed,
|
||||
zkp::{AsFieldElement, BfvSigned, BulletproofsField, ConstrainCmp, Field, FieldSpec},
|
||||
Cipher,
|
||||
},
|
||||
zkp_program, zkp_var, Compiler, FheZkpRuntime, PlainModulusConstraint, Result,
|
||||
};
|
||||
|
||||
#[fhe_program(scheme = "bfv")]
|
||||
fn update_balance(tx: Cipher<Signed>, balance: Signed) -> Cipher<Signed> {
|
||||
balance - tx
|
||||
}
|
||||
|
||||
#[zkp_program]
|
||||
fn valid_transaction<F: FieldSpec>(#[shared] tx: BfvSigned<F>, #[public] balance: Field<F>) {
|
||||
let tx = tx.into_field_elem();
|
||||
|
||||
// Constraint that tx is less than or equal to balance
|
||||
tx.constrain_le_bounded(balance, 64);
|
||||
|
||||
// Constraint that tx is greater than zero
|
||||
tx.constrain_gt_bounded(zkp_var!(0), 64);
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
println!("Compiling FHE and ZKP programs...");
|
||||
let app = Compiler::new()
|
||||
.fhe_program(update_balance)
|
||||
// this is not strictly necessary, but will help performance
|
||||
.plain_modulus_constraint(PlainModulusConstraint::Raw(1024))
|
||||
.zkp_backend::<BulletproofsBackend>()
|
||||
.zkp_program(valid_transaction)
|
||||
.compile()?;
|
||||
let rt = FheZkpRuntime::new(app.params(), &BulletproofsBackend::new())?;
|
||||
|
||||
let valid_tx_zkp = app.get_zkp_program(valid_transaction).unwrap();
|
||||
|
||||
println!("Generating FHE keys...");
|
||||
let (public_key, _secret_key) = rt.generate_keys()?;
|
||||
|
||||
let balance = 2002_i64;
|
||||
let tx = 5_i64;
|
||||
println!("Using balance {balance} and tx {tx}...");
|
||||
let balance = BulletproofsField::from(balance);
|
||||
let tx = Signed::from(tx);
|
||||
|
||||
let mut proof_builder = LogProofBuilder::new(&rt);
|
||||
|
||||
println!("Encrypting and sharing transaction...");
|
||||
let (_ct, tx_msg) = proof_builder.encrypt_and_share(&tx, &public_key)?;
|
||||
|
||||
println!("Building linkedproof...");
|
||||
let lp = proof_builder
|
||||
.zkp_program(valid_tx_zkp)?
|
||||
.shared_input(&tx_msg)
|
||||
.public_input(balance)
|
||||
.build_linkedproof()?;
|
||||
|
||||
println!("Verifying linkedproof...");
|
||||
lp.verify(valid_tx_zkp, vec![BulletproofsField::from(balance)], vec![])?;
|
||||
|
||||
println!("Success! Transaction is valid.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn main_works() -> Result<()> {
|
||||
main()
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
# Linked SDLP and R1CS proofs
|
||||
|
||||
This function creates a linked proof between a short discrete log proof (SDLP) and a R1CS bulletproof. An example use case is proving an encryption is valid (by SDLP) and that the encrypted message has some property (by R1CS Bulletproof).
|
||||
|
||||
The SDLP is used to prove a linear relation while keeping part of that relation secret. Specifically, the SDLP allows one to prove a matrix relation of the form \\(A * S = T\\), where \\(S\\) is a matrix of secrets (sometimes also called a witness) and \\(T\\) is the result of computing \\(A\\) on that secret. An example relation is the equation for encryption in BFV, which can be used to show that a ciphertext is a valid encryption of some underlying message instead of a random value.
|
||||
|
||||
R1CS bulletproofs enable proving arbitrary arithmetic circuits, which can be used to prove that some secret satisfies some property. For example, one can prove that a private transaction can occur because the sender has enough funds to cover the transaction, without revealing what the transaction is.
|
||||
|
||||
Combining these two proofs is powerful because it allows one to prove both that a ciphertext is a valid encryption of some message and that the message satisfies some property. In the prior example of a private transaction, with a linked proof we can now prove that the sender knows the value in an encrypted transaction and that the sender has enough funds to cover the transaction, without decrypting the transaction.
|
||||
|
||||
How does this work in practice? We will first generate a lattice problem of the form \\(A * S = T\\) and then specify what parts of S are shared with the ZKP program. We then specify the remaining private inputs to the ZKP program, the public inputs to the ZKP, and the constant inputs to the ZKP.
|
||||
|
||||
|
||||
# Example
|
||||
|
||||
Let's perform a transaction where the transaction amount is private and the balance is public. We want to prove that the transaction is valid (i.e. the transaction amount is less than or equal to the balance) without revealing the transaction amount.
|
||||
|
||||
Let's first tackle the SDLP to show that we can generate a valid encryption of a message. The BFV encryption equation in SEAL is
|
||||
|
||||
\\[
|
||||
(c_0, c_1) = (\Delta m + r + p_0 u + e_1,\ p_1 u + e_2)
|
||||
\\]
|
||||
|
||||
where
|
||||
|
||||
* \\(\Delta\\) is a constant polynomial with \\(\lfloor q/t \rfloor\\) as it's DC component. \\(q\\) is the coefficient modulus and t is the plain modulus,
|
||||
* \\(m\\) is the polynomial plaintext message to encrypt,
|
||||
* \\(r\\) is a rounding polynomial proportional to m with coefficients in the range \\([0, t]\\),
|
||||
* \\(p_0\\) and \\(p_1\\) are the public key polynomials,
|
||||
* \\(u\\) is a random ternary polynomial,
|
||||
* \\(e_1\\) and \\(e_2\\) are random polynomials sampled from the centered binomial distribution, and
|
||||
* \\(c_0\\) and \\(c_1\\) are the ciphertext polynomials.
|
||||
|
||||
This can be implemented as a linear relation as follows.
|
||||
|
||||
\\[
|
||||
\begin{aligned}
|
||||
\begin{bmatrix}
|
||||
\Delta & 1 & p_0 & 1 & 0 \\\\
|
||||
0 & 0 & p_1 & 0 & 1 \\\\
|
||||
\end{bmatrix}
|
||||
\begin{bmatrix}
|
||||
m \\\\ r \\\\ u \\\\ e_1 \\\\ e_2
|
||||
\end{bmatrix}
|
||||
=
|
||||
\begin{bmatrix}
|
||||
c_0 \\\\ c_1
|
||||
\end{bmatrix}
|
||||
\end{aligned}
|
||||
\\]
|
||||
|
||||
To perform the SDLP, we will need to specify the bounds for each coefficient of each element of S. In the case where we encode m as a constant polynomial (ie the plaintext is a constant in the DC coefficient and zero for all other coefficients), the bounds for \\(m\\) are `[t, 0, ..., 0]`, while the bounds for the other components are based on their respective distributions.
|
||||
|
||||
Here is an example for generative the BFV encryption linear relation of an unsigned number using the `logproof::test::seal_bfv_encryption_linear_relation` function.
|
||||
|
||||
```rust
|
||||
# use logproof::rings::SealQ128_1024;
|
||||
# use logproof::test::seal_bfv_encryption_linear_relation;
|
||||
let transaction = 10_000u64;
|
||||
|
||||
// Generate a lattice problem for A * S = T (mod f). The bounds for each
|
||||
// coefficient of each element of S are calculated in the function.
|
||||
let lattice_problem = seal_bfv_encryption_linear_relation::<SealQ128_1024, 1>(
|
||||
transaction,
|
||||
1024, // Lattice dimension
|
||||
12289, // Plaintext modulus
|
||||
false // Use the single encoder instead of the batch encoder
|
||||
);
|
||||
```
|
||||
|
||||
The second proof we would like to show is that the encrypted value is less than some balance. We can do that using the Sunscreen compiler and the following ZKP.
|
||||
|
||||
```rust
|
||||
# use sunscreen::{zkp_var, zkp_program};
|
||||
# use sunscreen::types::zkp::{Field, ProgramNode};
|
||||
# use sunscreen_zkp_backend::FieldSpec;
|
||||
# use crate::sunscreen::types::zkp::ConstrainCmp;
|
||||
#
|
||||
# fn from_twos_complement_field_element<F: FieldSpec, const N: usize>(
|
||||
# x: [ProgramNode<Field<F>>; N],
|
||||
# ) -> ProgramNode<Field<F>> {
|
||||
# let mut x_recon = zkp_var!(0);
|
||||
#
|
||||
# for (i, x_i) in x.iter().enumerate().take(N - 1) {
|
||||
# x_recon = x_recon + (zkp_var!(2i64.pow(i as u32)) * (*x_i));
|
||||
# }
|
||||
#
|
||||
# x_recon = x_recon + zkp_var!(-(2i64.pow((N - 1) as u32))) * x[N - 1];
|
||||
#
|
||||
# x_recon
|
||||
# }
|
||||
#
|
||||
#[zkp_program]
|
||||
fn valid_transaction<F: FieldSpec>(#[private] x: [Field<F>; 15], #[public] balance: Field<F>) {
|
||||
let lower_bound = zkp_var!(0);
|
||||
|
||||
// Reconstruct x from the bag of bits
|
||||
let x_recon = from_twos_complement_field_element(x);
|
||||
|
||||
// Constraint that x is less than or equal to balance
|
||||
balance.constrain_ge_bounded(x_recon, 64);
|
||||
|
||||
// Constraint that x is greater than or equal to zero
|
||||
lower_bound.constrain_le_bounded(x_recon, 64);
|
||||
}
|
||||
```
|
||||
|
||||
Interestingly the transaction amount is not specified as a number but in its twos complement binary representation. This is because in the SDLP, the message polynomial is expanded into its twos complement binary and then used as an input to the proof. In order to link the SDLP and the ZKP program, we will be sharing this binary expansion between the two proof systems. This requires the ZKP to convert the binary expanded message polynomial back into something meaningful. It is important to note that the ZKP has to know the number of bits in the message polynomial expansion at compile time, hence the raw `15` number above.
|
||||
|
||||
In this particular example (a constant polynomial message with bounds on the DC component only), we can use this helper function to reconstitute the transaction amount.
|
||||
|
||||
```rust
|
||||
# use sunscreen::zkp_var;
|
||||
# use sunscreen::types::zkp::{Field, ProgramNode};
|
||||
# use sunscreen_zkp_backend::FieldSpec;
|
||||
#
|
||||
fn from_twos_complement_field_element<F: FieldSpec, const N: usize>(
|
||||
x: [ProgramNode<Field<F>>; N],
|
||||
) -> ProgramNode<Field<F>> {
|
||||
let mut x_recon = zkp_var!(0);
|
||||
|
||||
for (i, x_i) in x.iter().enumerate().take(N - 1) {
|
||||
x_recon = x_recon + (zkp_var!(2i64.pow(i as u32)) * (*x_i));
|
||||
}
|
||||
|
||||
x_recon = x_recon + zkp_var!(-(2i64.pow((N - 1) as u32))) * x[N - 1];
|
||||
|
||||
x_recon
|
||||
}
|
||||
```
|
||||
|
||||
With all of these pieces, we can use the `LinkedProof::create` function to generate
|
||||
a proof that the encrypted transaction amount is less than or equal to the
|
||||
balance.
|
||||
|
||||
```rust
|
||||
# use sunscreen::{Compiler, zkp_var, zkp_program};
|
||||
# use sunscreen::types::zkp::{BulletproofsField, Field, ProgramNode};
|
||||
# use sunscreen_zkp_backend::FieldSpec;
|
||||
# use crate::sunscreen::types::zkp::ConstrainCmp;
|
||||
# use sunscreen_zkp_backend::bulletproofs::BulletproofsBackend;
|
||||
# use logproof::test::seal_bfv_encryption_linear_relation;
|
||||
# use logproof::rings::SealQ128_1024;
|
||||
# use sunscreen_runtime::LinkedProof;
|
||||
#
|
||||
# fn from_twos_complement_field_element<F: FieldSpec, const N: usize>(
|
||||
# x: [ProgramNode<Field<F>>; N],
|
||||
# ) -> ProgramNode<Field<F>> {
|
||||
# let mut x_recon = zkp_var!(0);
|
||||
#
|
||||
# for (i, x_i) in x.iter().enumerate().take(N - 1) {
|
||||
# x_recon = x_recon + (zkp_var!(2i64.pow(i as u32)) * (*x_i));
|
||||
# }
|
||||
#
|
||||
# x_recon = x_recon + zkp_var!(-(2i64.pow((N - 1) as u32))) * x[N - 1];
|
||||
#
|
||||
# x_recon
|
||||
# }
|
||||
#
|
||||
# #[zkp_program]
|
||||
# fn valid_transaction<F: FieldSpec>(#[private] x: [Field<F>; 15], #[public] balance: Field<F>) {
|
||||
# let lower_bound = zkp_var!(0);
|
||||
#
|
||||
# // Reconstruct x from the bag of bits
|
||||
# let x_recon = from_twos_complement_field_element(x);
|
||||
#
|
||||
# // Constraint that x is less than or equal to balance
|
||||
# balance.constrain_ge_bounded(x_recon, 64);
|
||||
#
|
||||
# // Constraint that x is greater than or equal to zero
|
||||
# lower_bound.constrain_le_bounded(x_recon, 64);
|
||||
# }
|
||||
#
|
||||
let app = Compiler::new()
|
||||
.zkp_backend::<BulletproofsBackend>()
|
||||
.zkp_program(valid_transaction)
|
||||
.compile().expect("ZKP program did not compile");
|
||||
|
||||
// Compile the ZKP program
|
||||
let valid_transaction_zkp = app.get_zkp_program(valid_transaction).unwrap();
|
||||
|
||||
// Private and public inputs
|
||||
let x = 10_000u64;
|
||||
let balance = 12_000u64;
|
||||
|
||||
// Generate the SDLP linear relation and specify that the message part of S
|
||||
// should be shared.
|
||||
let sdlp = seal_bfv_encryption_linear_relation::<SealQ128_1024, 1>(x, 1024, 12289, false);
|
||||
let shared_indices = vec![(0, 0)];
|
||||
|
||||
// This will generate an proof of type `LinkedProof`
|
||||
|
||||
println!("Performing linked proof");
|
||||
let lp = LinkedProof::create(
|
||||
&sdlp,
|
||||
&shared_indices,
|
||||
valid_transaction_zkp,
|
||||
vec![],
|
||||
vec![BulletproofsField::from(balance)],
|
||||
vec![],
|
||||
)
|
||||
.unwrap();
|
||||
println!("Linked proof done");
|
||||
|
||||
// that can be verified as follows:
|
||||
|
||||
println!("Performing linked verify");
|
||||
lp.verify(
|
||||
valid_transaction_zkp,
|
||||
vec![BulletproofsField::from(balance)],
|
||||
vec![],
|
||||
)
|
||||
.expect("Failed to verify linked proof");
|
||||
println!("Linked verify done");
|
||||
```
|
||||
@@ -41,8 +41,7 @@ mod error;
|
||||
mod params;
|
||||
|
||||
#[cfg(feature = "linkedproofs")]
|
||||
#[doc = include_str!("../docs/linked.md")]
|
||||
pub mod linked {}
|
||||
pub mod linked;
|
||||
|
||||
/// This module contains types used internally when compiling [`fhe_program`]s.
|
||||
pub mod fhe;
|
||||
|
||||
27
sunscreen/src/linked.rs
Normal file
27
sunscreen/src/linked.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
//! A linked proof consists of a short discrete log proof (SDLP) and an R1CS bulletproof (BP). It
|
||||
//! allows you to simultaneously prove an encryption is valid (SDLP) and that the encrypted message
|
||||
//! has some property (BP).
|
||||
//!
|
||||
//! The SDLP proves a linear relation while keeping part of that relation secret. Specifically, the
|
||||
//! SDLP allows one to prove a matrix relation of the form \\(A \cdot S = T\\), where \\(S\\) is a
|
||||
//! matrix of secrets (sometimes also called a witness) and \\(T\\) is the result of computing
|
||||
//! \\(A\\) on that secret. An example relation is the equation for encryption in BFV, which can
|
||||
//! be used to show that a ciphertext is a valid encryption of some known underlying message.
|
||||
//!
|
||||
//! The BP enables proving arbitrary arithmetic circuits, which can be used to prove that a secret
|
||||
//! satisfies some property. For example, one can prove that a private transaction can occur
|
||||
//! because the sender has enough funds to cover the transaction, without revealing what the
|
||||
//! transaction is.
|
||||
//!
|
||||
//! Combining these two proofs is powerful because it allows one to prove both that a ciphertext is
|
||||
//! a valid encryption of some message and that the message satisfies some property. In the prior
|
||||
//! example of a private transaction, with a linked proof we can now prove that the sender knows
|
||||
//! the value in an encrypted transaction and that the sender has enough funds to cover the
|
||||
//! transaction, without decrypting the transaction.
|
||||
//!
|
||||
//! How does this work in practice? If you use our [builder](`LogProofBuilder`), you
|
||||
//! can encrypt messages in a very similar way to our typical [runtime
|
||||
//! encryption](crate::FheRuntime::encrypt), while also opting to _share_ a message with a linked
|
||||
//! ZKP program. Under the hood, we'll handle the complicated bits of generating a linear relation
|
||||
//! for SDLP and sharing the secrets with the [`zkp_program`](crate::zkp_program).
|
||||
pub use sunscreen_runtime::{LinkedProof, LogProofBuilder, Sdlp};
|
||||
@@ -45,7 +45,7 @@ struct BP {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Linked proof between a SDLP and R1CS BP
|
||||
/// A linked proof between an SDLP and R1CS BP
|
||||
pub struct LinkedProof {
|
||||
sdlp: Sdlp,
|
||||
bp: BP,
|
||||
@@ -104,26 +104,19 @@ fn new_single_party_with_shared_generators(
|
||||
|
||||
impl LinkedProof {
|
||||
const TRANSCRIPT_LABEL: &'static [u8] = b"linked-sdlp-and-r1cs-bp";
|
||||
/**
|
||||
* This function creates a linked proof between a short discrete log proof
|
||||
* (SDLP) and a R1CS bulletproof. An example use case is proving an encryption
|
||||
* is valid (by SDLP) and that the encrypted message has some property (by R1CS
|
||||
* Bulletproof).
|
||||
*
|
||||
* Note that the [builder methods](`crate::LogProofBuilder`) offer an easier way to construct this
|
||||
* proof. See the user documentation for more information.
|
||||
*
|
||||
* Arguments:
|
||||
*
|
||||
* * `lattice_problem`: The lattice problem to prove
|
||||
* * `shared_indices`: The indices of the shared values between the SDLP and the
|
||||
* R1CS bulletproof
|
||||
* * `program`: The compiled ZKP program to prove
|
||||
* * `private_inputs`: The private inputs to the ZKP program, not including the
|
||||
* shared values
|
||||
* * `public_inputs`: The public inputs to the ZKP program
|
||||
* * `constant_inputs`: The constant inputs to the ZKP program
|
||||
*/
|
||||
/// This function creates a linked proof.
|
||||
///
|
||||
/// Note that the [builder methods](`crate::LogProofBuilder`) offer an easier way to construct this
|
||||
/// proof. See the user documentation for more information.
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `prover_knowledge`: The SDLP prover knowledge
|
||||
/// * `shared_indices`: The indices of the shared values between the SDLP witness matrix `S`
|
||||
/// and the R1CS bulletproof
|
||||
/// * `program`: The compiled ZKP program to prove
|
||||
/// * `private_inputs`: The private inputs to the ZKP program, not including the shared values
|
||||
/// * `public_inputs`: The public inputs to the ZKP program
|
||||
/// * `constant_inputs`: The constant inputs to the ZKP program
|
||||
pub fn create<I>(
|
||||
prover_knowledge: &SealSdlpProverKnowledge,
|
||||
shared_indices: &[(usize, usize)],
|
||||
@@ -254,20 +247,16 @@ impl LinkedProof {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* This function verifies a linked proof between a short discrete log proof
|
||||
* (SDLP) and a R1CS bulletproof. An example use case is proving an encryption
|
||||
* is valid (by SDLP) and that the encrypted message has some property (by R1CS
|
||||
* Bulletproof).
|
||||
*
|
||||
* See the main documentation for more information and examples.
|
||||
*
|
||||
* Arguments:
|
||||
*
|
||||
* * `program`: The compiled ZKP program to verify
|
||||
* * `public_inputs`: The public inputs to the ZKP program
|
||||
* * `constant_inputs`: The constant inputs to the ZKP program
|
||||
*/
|
||||
/// This function verifies the linked proof.
|
||||
///
|
||||
/// See the main documentation for more information and examples.
|
||||
///
|
||||
/// Arguments:
|
||||
///
|
||||
/// * `program`: The compiled ZKP program to verify
|
||||
/// * `public_inputs`: The public inputs to the ZKP program
|
||||
/// * `constant_inputs`: The constant inputs to the ZKP program
|
||||
///
|
||||
pub fn verify<I>(
|
||||
&self,
|
||||
program: &CompiledZkpProgram,
|
||||
|
||||
Reference in New Issue
Block a user