Proof linking (#339)

This commit is contained in:
Sam Tay
2024-02-03 15:01:24 -05:00
committed by GitHub
parent 7e17f2f3f7
commit 18ea59b542
49 changed files with 2507 additions and 1470 deletions

View File

@@ -44,19 +44,31 @@ jobs:
# Checks are ordered from fastest to slowest so your build fails quickly on invalid PRs
# We do everything in release mode so tests run quickly and steps cache each other.
# Check the submitted change meets style guidelines
- name: Cargo Format
run: cargo fmt --check
# Check that common feature permutations compile
- name: Core compile check
run: cargo check --release
- name: Full compile check
run: cargo check --release --features deterministic,linkedproofs
# Build and run the tests
- name: Build and run tests
run: cargo test --workspace --verbose --release --features deterministic,linkedproofs
- name: Verify examples outside of workspace (allowlist_zkp)
run: cargo test --workspace --verbose
run: cargo test --workspace --verbose --release
working-directory: ./examples/allowlist_zkp
# Build package in prep for user docs
- name: Build sunscreen and bincode
run: cargo build --release --features bulletproofs,linkedproofs --package sunscreen --package bincode
# Build mdbook
- name: Build mdBook
run: cargo build --release
working-directory: ./mdBook
# Build user documentation
- name: Test docs
run: ../mdBook/target/release/mdbook test -L dependency=../target/release/deps --extern sunscreen=../target/release/libsunscreen.rlib --extern bincode=../target/release/libbincode.rlib
working-directory: ./sunscreen_docs
@@ -101,14 +113,11 @@ jobs:
restore-keys: |
${{ runner.os }}-cargo-doc-
${{ runner.os }}-cargo-
# Cursory check to ensure your CL contains valid Rust code
- name: Cargo check
run: cargo check --release
# Check the documentation builds, links work, etc.
# Check the full documentation builds, links work, etc.
- name: Cargo doc
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --release --no-deps
run: cargo doc --release --no-deps --features bulletproofs,linkedproofs
emscripten:
runs-on: linux-8core
steps:
@@ -138,4 +147,5 @@ jobs:
- name: Add Rust wasm32-unknown-emscripten target
run: rustup target add wasm32-unknown-emscripten
- name: Build AMM target for Emscripten
run: source emsdk/emsdk/emsdk_env.sh; cargo build --bin amm --target wasm32-unknown-emscripten --release
run: source ../../emsdk/emsdk/emsdk_env.sh; cargo build --target wasm32-unknown-emscripten --release
working-directory: ./examples/amm

16
Cargo.lock generated
View File

@@ -1622,6 +1622,7 @@ dependencies = [
"sha3 0.10.8",
"sunscreen_curve25519",
"sunscreen_math",
"thiserror",
]
[[package]]
@@ -2180,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"
@@ -2570,6 +2578,12 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
[[package]]
name = "seq-macro"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
[[package]]
name = "serde"
version = "1.0.189"
@@ -2945,11 +2959,13 @@ dependencies = [
"log",
"logproof",
"merlin",
"paste",
"petgraph",
"rayon",
"rlp",
"seal_fhe",
"semver",
"seq-macro",
"serde",
"serde_json",
"static_assertions",

View File

@@ -295,7 +295,10 @@ mod tests {
use super::*;
#[test]
fn main_works() -> Result<(), Error> {
main()
fn main_works() {
let ten_mb = 10 * 1024 * 1024;
let builder = std::thread::Builder::new().stack_size(ten_mb);
let handler = builder.spawn(main).unwrap();
handler.join().unwrap().unwrap();
}
}

View File

@@ -27,7 +27,7 @@ use sunscreen::{
/// Let's try an example. Suppose `p(x) = (x^4 - 4)` and `q(x) = (x^2 - 3)` are polynomials in
/// `Z[x]/(x^5 + 1)`. Then
///
/// ```ignore
/// ```text
/// p(x) * q(x) = (x^4 - 4) * (x^2 - 3)
/// = x^6 - 3x^4 - 4x^2 + 12
/// = -x - 3x^4 - 4x^2 + 12

View File

@@ -0,0 +1,7 @@
[package]
name = "private_tx_linkedproof"
version = "0.1.0"
edition = "2021"
[dependencies]
sunscreen = { path = "../../sunscreen", features = ["linkedproofs"] }

View 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>(#[linked] 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_link(&tx, &public_key)?;
println!("Building linkedproof...");
let lp = proof_builder
.zkp_program(valid_tx_zkp)?
.linked_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()
}
}

View File

@@ -18,6 +18,7 @@ rayon = { workspace = true }
serde = { workspace = true }
sunscreen_math = { workspace = true }
seal_fhe = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
bincode = { workspace = true }

View File

@@ -1,7 +1,7 @@
//! This module provides a mid-level API for generating SDLP prover and verifier knowledge from BFV
//! encryptions, all at the [`seal_fhe`] layer.
use std::{fmt::Debug, marker::PhantomData, ops::Neg};
use std::{borrow::Cow, fmt::Debug, ops::Neg};
use crypto_bigint::{NonZero, Uint};
use seal_fhe::{
@@ -32,14 +32,14 @@ const S_COEFFICIENT_BOUND: u64 = 2;
/// higher level. A single Sunscreen plaintext may actually encode multiple SEAL plaintexts, and
/// hence multiple proof statements.
#[derive(Debug)]
pub enum BfvProofStatement<C, P> {
pub enum BfvProofStatement<'p> {
/// A statement that the ciphertext symmetrically encrypts the identified message.
PrivateKeyEncryption {
/// Column index in the A matrix, or equivalently the index of the message slice provided
/// when generating the prover knowledge.
message_id: usize,
/// The ciphertext of the encryption statement.
ciphertext: C,
ciphertext: Ciphertext,
},
/// A statement that the ciphertext asymmetrically encrypts the identified message.
PublicKeyEncryption {
@@ -47,13 +47,13 @@ pub enum BfvProofStatement<C, P> {
/// when generating the prover knowledge.
message_id: usize,
/// The ciphertext of the encryption statement.
ciphertext: C,
ciphertext: Ciphertext,
/// The public key of the encryption statement.
public_key: P,
public_key: Cow<'p, PublicKey>,
},
}
impl<C, P> BfvProofStatement<C, P> {
impl<'p> BfvProofStatement<'p> {
/// Get the message index of this statement.
pub fn message_id(&self) -> usize {
match self {
@@ -63,7 +63,7 @@ impl<C, P> BfvProofStatement<C, P> {
}
/// Get the ciphertext of this statement.
pub fn ciphertext(&self) -> &C {
pub fn ciphertext(&self) -> &Ciphertext {
match self {
BfvProofStatement::PrivateKeyEncryption { ciphertext, .. } => ciphertext,
BfvProofStatement::PublicKeyEncryption { ciphertext, .. } => ciphertext,
@@ -83,11 +83,11 @@ impl<C, P> BfvProofStatement<C, P> {
/// A witness for a [`BfvProofStatement`].
#[derive(Debug)]
pub enum BfvWitness<S> {
pub enum BfvWitness<'s> {
/// A witness for the [`BfvProofStatement::PrivateKeyEncryption`] variant.
PrivateKeyEncryption {
/// The private key used for the encryption.
private_key: S,
private_key: Cow<'s, SecretKey>,
/// Gaussian error polynomial
///
/// N.B. this polynomial array should always have size one, see note below.
@@ -114,6 +114,23 @@ pub enum BfvWitness<S> {
},
}
/// A BFV message, which is a SEAL plaintext and an optional coefficient bound.
#[derive(Debug)]
pub struct BfvMessage {
/// The plaintext message.
pub plaintext: Plaintext,
/// An optional bound on the plaintext message.
///
/// By default, we use a conservative coefficient bound equal to the plaintext modulus for
/// every coefficient in the lattice dimension. This is a _much_ higher bound than is
/// necessary for common numeric plaintext encodings. For example, if you are encoding a
/// 64-bit signed integer in 2s complement, you likely don't need 1024 coefficients to be
/// nonzero.
///
/// Note that the bounds should be a vector of length equal to the lattice dimension.
pub bounds: Option<Bounds>,
}
type Z<const N: usize, B> = Zq<N, BarrettBackend<N, B>>;
/// Generate the full [`LogProofProverKnowledge`] for a given set of [`BfvProofStatement`]s.
@@ -184,21 +201,22 @@ type Z<const N: usize, B> = Zq<N, BarrettBackend<N, B>>;
/// encryptions (public or private) of a single plaintext message, like we do for the delta scaling
/// parameter. However, since the remainder is held in each [`BfvWitness`], I've gone with the less
/// surprising implementation where we have a remainder witness for each statement.
pub fn generate_prover_knowledge<C, P, S, T, B, const N: usize>(
statements: &[BfvProofStatement<C, P>],
messages: &[Plaintext], // may want messages AsRef as well.. we'll see
witness: &[BfvWitness<S>],
params: &T,
pub fn generate_prover_knowledge<P, B, const N: usize>(
statements: &[BfvProofStatement<'_>],
messages: &[BfvMessage],
witness: &[BfvWitness<'_>],
params: &P,
ctx: &Context,
) -> LogProofProverKnowledge<Z<N, B>>
where
B: BarrettConfig<N>,
C: AsRef<Ciphertext>,
P: AsRef<PublicKey>,
S: AsRef<SecretKey>,
T: StatementParams,
P: StatementParams,
{
let vk = generate_verifier_knowledge(statements, params, ctx);
let bounds = messages
.iter()
.map(|m| m.bounds.clone())
.collect::<Vec<_>>();
let vk = generate_verifier_knowledge(statements, &bounds, params, ctx);
let s = compute_s(statements, messages, witness);
@@ -208,20 +226,19 @@ where
/// Generate only the [`LogProofVerifierKnowledge`] for a given set of [`BfvProofStatement`]s.
///
/// See the documentation for [`generate_prover_knowledge`] for more information.
pub fn generate_verifier_knowledge<C, P, T, B, const N: usize>(
statements: &[BfvProofStatement<C, P>],
params: &T,
pub fn generate_verifier_knowledge<P, B, const N: usize>(
statements: &[BfvProofStatement<'_>],
msg_bounds: &[Option<Bounds>],
params: &P,
ctx: &Context,
) -> LogProofVerifierKnowledge<Z<N, B>>
where
B: BarrettConfig<N>,
C: AsRef<Ciphertext>,
P: AsRef<PublicKey>,
T: StatementParams,
P: StatementParams,
{
let a = compute_a(statements, params, ctx);
let t = compute_t(statements, ctx);
let bounds = compute_bounds(statements, params);
let bounds = compute_bounds(statements, msg_bounds, params);
let f = Polynomial {
coeffs: {
let degree = params.degree() as usize;
@@ -235,16 +252,14 @@ where
LogProofVerifierKnowledge { a, t, bounds, f }
}
fn compute_a<C, P, T, B, const N: usize>(
statements: &[BfvProofStatement<C, P>],
params: &T,
fn compute_a<P, B, const N: usize>(
statements: &[BfvProofStatement<'_>],
params: &P,
ctx: &Context,
) -> PolynomialMatrix<Z<N, B>>
where
B: BarrettConfig<N>,
C: AsRef<Ciphertext>,
P: AsRef<PublicKey>,
T: StatementParams,
P: StatementParams,
{
let mut offsets = IdxOffsets::new(statements);
let (rows, cols) = offsets.a_shape();
@@ -266,7 +281,7 @@ where
match s {
// sk, e blocks
BfvProofStatement::PrivateKeyEncryption { ciphertext, .. } => {
let c1 = (ctx, ciphertext.as_ref()).as_poly_vec().pop().unwrap();
let c1 = (ctx, ciphertext).as_poly_vec().pop().unwrap();
a.set(row, offsets.private_a, c1);
a.set(row, offsets.private_e, Polynomial::one());
offsets.inc_private();
@@ -292,21 +307,20 @@ where
a
}
fn compute_s<C, P, S, B, const N: usize>(
statements: &[BfvProofStatement<C, P>],
messages: &[Plaintext],
witness: &[BfvWitness<S>],
fn compute_s<B, const N: usize>(
statements: &[BfvProofStatement<'_>],
messages: &[BfvMessage],
witness: &[BfvWitness<'_>],
) -> PolynomialMatrix<Z<N, B>>
where
B: BarrettConfig<N>,
S: AsRef<SecretKey>,
{
let mut offsets = IdxOffsets::new(statements);
let mut s = PolynomialMatrix::new(offsets.a_shape().1, 1);
// m_i block
for (i, m) in messages.iter().enumerate() {
s.set(i, 0, m.as_poly());
s.set(i, 0, m.plaintext.as_poly());
}
// r_i, u_i, e_i, sk, e blocks
@@ -342,18 +356,17 @@ where
s
}
fn compute_t<C, P, B, const N: usize>(
statements: &[BfvProofStatement<C, P>],
fn compute_t<B, const N: usize>(
statements: &[BfvProofStatement<'_>],
ctx: &Context,
) -> PolynomialMatrix<Z<N, B>>
where
B: BarrettConfig<N>,
C: AsRef<Ciphertext>,
{
let rows = statements
.iter()
.flat_map(|s| {
let mut c = (ctx, s.ciphertext().as_ref()).as_poly_vec();
let mut c = (ctx, s.ciphertext()).as_poly_vec();
// only include first ciphertext element for private statements
if s.is_private() {
c.pop().unwrap();
@@ -370,24 +383,36 @@ where
t
}
fn compute_bounds<C, P, T>(statements: &[BfvProofStatement<C, P>], params: &T) -> Matrix<Bounds>
fn compute_bounds<P>(
statements: &[BfvProofStatement<'_>],
msg_bounds: &[Option<Bounds>],
params: &P,
) -> Matrix<Bounds>
where
T: StatementParams,
P: StatementParams,
{
let mut offsets = IdxOffsets::new(statements);
let mut bounds = Matrix::<Bounds>::new(offsets.a_shape().1, 1);
let degree = params.degree() as usize;
// calculate bounds
let m_bound = Bounds(vec![params.plain_modulus(); degree]);
let r_bound = m_bound.clone();
let m_default_bound = Bounds(vec![params.plain_modulus(); degree]);
let r_bound = m_default_bound.clone();
let u_bound = Bounds(vec![U_COEFFICIENT_BOUND; degree]);
let e_bound = Bounds(vec![E_COEFFICIENT_BOUND; degree]);
let s_bound = Bounds(vec![S_COEFFICIENT_BOUND; degree]);
// insert them
for i in 0..IdxOffsets::num_messages(statements) {
bounds.set(i, 0, m_bound.clone());
bounds.set(
i,
0,
msg_bounds
.get(i)
.and_then(|o| o.as_ref())
.unwrap_or(&m_default_bound)
.clone(),
);
}
for s in statements {
bounds.set(offsets.remainder, 0, r_bound.clone());
@@ -408,7 +433,7 @@ where
/// Represents the column offsets in `A` and the row offsets in `S` for the various fields.
//
// Hm. This could be an iterator that spits out the next ProofStatement with the appropriate indices.
struct IdxOffsets<C, P> {
struct IdxOffsets {
/// The remainder block occurs after the delta/message block.
remainder: usize,
/// The public key block occurs after the remainders.
@@ -421,11 +446,10 @@ struct IdxOffsets<C, P> {
private_a: usize,
/// The private key statement's error component block occurs last.
private_e: usize,
_phantom: PhantomData<(C, P)>,
}
impl<C, P> IdxOffsets<C, P> {
fn new(statements: &[BfvProofStatement<C, P>]) -> Self {
impl IdxOffsets {
fn new(statements: &[BfvProofStatement<'_>]) -> Self {
// Counts
let num_messages = Self::num_messages(statements);
let num_public = Self::num_public(statements);
@@ -447,7 +471,6 @@ impl<C, P> IdxOffsets<C, P> {
public_e_1,
private_a,
private_e,
_phantom: PhantomData,
}
}
@@ -475,18 +498,18 @@ impl<C, P> IdxOffsets<C, P> {
self.public_e_1 += 1;
}
fn num_messages(statements: &[BfvProofStatement<C, P>]) -> usize {
fn num_messages(statements: &[BfvProofStatement<'_>]) -> usize {
statements
.iter()
.fold(0usize, |max, s| usize::max(max, s.message_id()))
+ 1
}
fn num_private(statements: &[BfvProofStatement<C, P>]) -> usize {
fn num_private(statements: &[BfvProofStatement<'_>]) -> usize {
statements.iter().filter(|s| s.is_private()).count()
}
fn num_public(statements: &[BfvProofStatement<C, P>]) -> usize {
fn num_public(statements: &[BfvProofStatement<'_>]) -> usize {
statements.len() - Self::num_private(statements)
}
}
@@ -652,32 +675,17 @@ mod tests {
#[test]
fn idx_offsets() {
// recreating the example in the docs
let statements = vec![
BfvProofStatement::PublicKeyEncryption {
message_id: 0,
ciphertext: 0xdeadbeef_u32,
public_key: 100_u32,
},
BfvProofStatement::PublicKeyEncryption {
message_id: 1,
ciphertext: 0xdeedbeaf_u32,
public_key: 100_u32,
},
BfvProofStatement::PrivateKeyEncryption {
message_id: 0,
ciphertext: 0xbeefdead_u32,
},
];
let idx_offsets = IdxOffsets::new(&statements);
// TODO create the example in the docs after symmetric unlocked. for now, just public.
let ctx = BFVTestContext::new();
let test_fixture = ctx.random_fixture_with(3, 1);
let idx_offsets = IdxOffsets::new(&test_fixture.statements);
assert_eq!(idx_offsets.remainder, 2);
assert_eq!(idx_offsets.public_key, 2 + 3);
assert_eq!(idx_offsets.public_e_0, 2 + 3 + 2);
assert_eq!(idx_offsets.public_e_1, 2 + 3 + 2 + 2);
assert_eq!(idx_offsets.private_a, 2 + 3 + 2 + 2 + 2);
assert_eq!(idx_offsets.private_e, 2 + 3 + 2 + 2 + 2 + 1);
assert_eq!(idx_offsets.public_e_0, 2 + 3 + 3);
assert_eq!(idx_offsets.public_e_1, 2 + 3 + 3 + 3);
assert_eq!(idx_offsets.private_a, 2 + 3 + 3 + 3 + 3);
assert_eq!(idx_offsets.private_e, 2 + 3 + 3 + 3 + 3);
}
#[test]
@@ -741,10 +749,10 @@ mod tests {
proof.verify(&mut v_t, &pk.vk, &gen.g, &gen.h, &u)
}
struct TestFixture {
statements: Vec<BfvProofStatement<Ciphertext, PublicKey>>,
messages: Vec<Plaintext>,
witness: Vec<BfvWitness<SecretKey>>,
struct TestFixture<'p, 's> {
statements: Vec<BfvProofStatement<'p>>,
messages: Vec<BfvMessage>,
witness: Vec<BfvWitness<'s>>,
}
struct BFVTestContext {
@@ -804,19 +812,25 @@ mod tests {
// all the messages
let messages = (0..num_msgs)
.map(|_| self.random_plaintext())
.map(|_| BfvMessage {
plaintext: self.random_plaintext(),
bounds: None,
})
.collect::<Vec<_>>();
let mut statements = Vec::with_capacity(num_statements);
let mut witness = Vec::with_capacity(num_statements);
// statements without duplicate messages
for (i, msg) in messages.iter().enumerate() {
let (ct, u, e, r) = self.encryptor.encrypt_return_components(msg).unwrap();
for (i, m) in messages.iter().enumerate() {
let (ct, u, e, r) = self
.encryptor
.encrypt_return_components(&m.plaintext)
.unwrap();
statements.push(BfvProofStatement::PublicKeyEncryption {
message_id: i,
ciphertext: ct,
public_key: self.public_key.clone(),
public_key: Cow::Borrowed(&self.public_key),
});
witness.push(BfvWitness::PublicKeyEncryption { u, e, r });
}
@@ -827,12 +841,12 @@ mod tests {
let i = rng.gen_range(0..num_msgs);
let (ct, u, e, r) = self
.encryptor
.encrypt_return_components(&messages[i])
.encrypt_return_components(&messages[i].plaintext)
.unwrap();
statements.push(BfvProofStatement::PublicKeyEncryption {
message_id: i,
ciphertext: ct,
public_key: self.public_key.clone(),
public_key: Cow::Borrowed(&self.public_key),
});
witness.push(BfvWitness::PublicKeyEncryption { u, e, r });
}

View File

@@ -1,4 +1,4 @@
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
/**
* An error that occurred when verifying a proof.
*/
@@ -6,10 +6,12 @@ pub enum ProofError {
/**
* Failed to verify a proof.
*/
#[error("Failed to verify proof")]
VerificationError,
/**
* The proof is malformed.
*/
#[error("The proof is malformed")]
MalformedProof,
}

View File

@@ -65,6 +65,7 @@ pub mod rings;
pub mod math;
mod transcript;
pub use merlin::Transcript;
pub use transcript::LogProofTranscript;
/**

View File

@@ -1119,6 +1119,9 @@ impl LogProof {
*
* This modifies bitvec in place.
*
* Valid values for `log_b` are u64 integers not equal to 1; this function will
* panic on any other input.
*
*/
fn to_2s_complement_single<B, const N: usize>(value: &Zq<N, B>, log_b: u64, bitvec: &mut BitVec)
where
@@ -1127,6 +1130,7 @@ impl LogProof {
if log_b == 0 {
return;
}
assert_ne!(log_b, 1);
let value = value.into_bigint();
let mod_div_2 = Zq::<N, B>::field_modulus_div_2();
@@ -1197,7 +1201,7 @@ impl LogProof {
// Make sure we have an equal number of values and bounds to serialize
assert_eq!(values.len(), log_b.len());
let mut bitvec = BitVec::with_capacity(values.len() * log_b.iter().sum::<u64>() as usize);
let mut bitvec = BitVec::with_capacity(log_b.iter().sum::<u64>() as usize);
// This code should not feature timing side-channels.
for (value, bound) in zip(values.iter(), log_b.iter()) {

View File

@@ -1,26 +1,22 @@
/**
* Types and functions for testing logproof setups. Not meant to be used in
* production, only for testing.
*/
use crypto_bigint::{NonZero, Uint};
use std::borrow::Cow;
use seal_fhe::{
BFVEncoder, BFVScalarEncoder, BfvEncryptionParametersBuilder, CoefficientModulus, Context,
Decryptor, Encryptor, KeyGenerator, Modulus, PlainModulus, PolynomialArray, SecurityLevel,
BFVScalarEncoder, BfvEncryptionParametersBuilder, CoefficientModulus, Context, Decryptor,
Encryptor, KeyGenerator, PlainModulus, SecurityLevel,
};
use sunscreen_math::{
poly::Polynomial,
ring::{ArithmeticBackend, BarrettBackend, BarrettConfig, Ring, Zq},
Zero,
ring::{BarrettBackend, BarrettConfig, Ring, Zq},
};
use crate::{linear_algebra::Matrix, math::make_poly, rings::ZqRistretto, Bounds};
use crate::{
bfv_statement::{generate_prover_knowledge, BfvMessage, BfvProofStatement, BfvWitness},
linear_algebra::Matrix,
Bounds, LogProofProverKnowledge,
};
/**
* All information for a problem of the form `AS = T` in `Z_q[X]/f`. Useful for
* demonstrating full knowledge proofs before performing zero knowledge proofs.
* Similar to [LogProofProverKnowledge](crate::LogProofProverKnowledge) except
* any field limb size is allowed.
*/
/// All information for a problem of the form `AS = T` in `Z_q[X]/f`. Useful for
/// demonstrating full knowledge proofs for small testing polynomials.
pub struct LatticeProblem<Q>
where
Q: Ring,
@@ -41,146 +37,6 @@ where
pub b: Matrix<Bounds>,
}
/**
* Remove an element trailing in a vector. This can be helpful for types
* like `DensePolynomial`, which do not work properly if the polynomials
* passed in have a leading polynomial coefficient of zero.
*/
pub fn strip_trailing_value<T>(mut v: Vec<T>, trim_value: T) -> Vec<T>
where
T: Eq,
{
while v.last().map_or(false, |c| *c == trim_value) {
v.pop();
}
v
}
/**
* Converts a polynomial known to have coefficients less than all of the
* moduli in its associated modulus set into regular integers. The main
* advantage here over using a polynomial in its normal field is that the
* polynomial can be moved to a new field without modulus switching.
*/
pub fn convert_to_smallint(
coeff_modulus: &[Modulus],
poly_array: PolynomialArray,
) -> Vec<Vec<i64>> {
let first_coefficient = coeff_modulus[0].value();
let rns = poly_array.as_rns_u64s().unwrap();
let num_polynomials = poly_array.num_polynomials() as usize;
let poly_modulus_degree = poly_array.poly_modulus_degree() as usize;
let coeff_modulus_size = poly_array.coeff_modulus_size() as usize;
let mut result = vec![vec![0; poly_modulus_degree]; num_polynomials];
// Clippy suggests this odd way of indexing so we are going with it.
for (i, r_i) in result.iter_mut().enumerate() {
for (j, r_i_j) in r_i.iter_mut().enumerate() {
let index = i * poly_modulus_degree * coeff_modulus_size + j;
let coeff = rns[index];
let small_coeff = if coeff > first_coefficient / 2 {
((coeff as i128) - (first_coefficient as i128)) as i64
} else {
coeff as i64
};
*r_i_j = small_coeff;
}
}
result
}
/**
* Convert a PolynomialArray of small coefficients into a vector of
* coefficients. Each outer vector element is one polynomial. Leading zeros are
* automatically trimmed.
*/
pub fn convert_to_small_coeffs(
coeff_modulus: &[Modulus],
poly_array: PolynomialArray,
) -> Vec<Vec<i64>> {
convert_to_smallint(coeff_modulus, poly_array)
.into_iter()
.map(|v| strip_trailing_value(v, 0))
.collect()
}
/**
* Convert a `PolynomialArray` to a vector of `DensePolynomial`, where all the
* coefficients are small (less than any of the constituent coefficient moduli).
*/
pub fn convert_to_polynomial_by_small_coeffs<Q>(
coeff_modulus: &[Modulus],
poly_array: PolynomialArray,
) -> Vec<Polynomial<Q>>
where
Q: Ring + From<u64>,
{
convert_to_small_coeffs(coeff_modulus, poly_array)
.into_iter()
.map(|v| make_poly(&v))
.collect::<Vec<Polynomial<Q>>>()
}
/**
* Converts a `PolynomialArray` into a vector of `DensePolynomial`
* regardless of the magnitude of the coefficients.
*/
pub fn convert_to_polynomial<B, const N: usize>(
poly_array: PolynomialArray,
) -> Vec<Polynomial<Zq<N, B>>>
where
B: ArithmeticBackend<N>,
{
let chunk_size = poly_array.coeff_modulus_size() as usize;
let bigint_values = poly_array
.as_multiprecision_u64s()
.unwrap()
.chunks(chunk_size)
// SEAL sometimes encodes a multiprecision integer with more limbs
// than needed. The trailing limbs can be safely removed since they
// are 0.
.map(|x| Uint::<N>::from_words(x[0..N].try_into().unwrap()))
.collect::<Vec<_>>();
bigint_values
.chunks(poly_array.poly_modulus_degree() as usize)
.map(|x| {
let leading_zeros_removed = strip_trailing_value(x.to_vec(), Uint::<N>::ZERO);
Polynomial {
coeffs: leading_zeros_removed
.iter()
.map(|y| Zq::try_from(*y).unwrap())
.collect::<Vec<_>>(),
}
})
.collect()
}
/**
* Calculate the $\Delta$ parameter (floor(q/t)) for the BFV encryption scheme.
* This is a public parameter in the BFV scheme.
*
* # Remarks
* Since Zq is technically a [`Ring`], division may not be defined.
* We calculate `q/t` using integer division.
*/
pub fn bfv_delta<const N: usize>(coeff_modulus: ZqRistretto, plaintext_modulus: u64) -> Uint<N> {
let plain_modulus_bigint = NonZero::new(Uint::from(plaintext_modulus)).unwrap();
let delta = coeff_modulus.into_bigint().div_rem(&plain_modulus_bigint).0;
let limbs = delta.as_limbs().map(|l| l.into());
Uint::<N>::from_words(limbs[0..N].try_into().unwrap())
}
/**
* Generate a lattice problem for the BFV scheme.
*/
@@ -188,32 +44,16 @@ pub fn seal_bfv_encryption_linear_relation<B, const N: usize>(
message: u64,
degree: u64,
plain_modulus: u64,
batch_encoder: bool,
) -> LatticeProblem<Zq<N, BarrettBackend<N, B>>>
) -> LogProofProverKnowledge<Zq<N, BarrettBackend<N, B>>>
where
B: BarrettConfig<N>,
{
let plain_modulus = PlainModulus::raw(plain_modulus).unwrap();
let coeff_modulus = CoefficientModulus::bfv_default(degree, SecurityLevel::TC128).unwrap();
// Calculate the data coefficient modulus, which for fields with more
// that one modulus in the coefficient modulus set is equal to the
// product of all but the last moduli in the set.
let mut data_modulus = ZqRistretto::from(1);
if coeff_modulus.len() == 1 {
data_modulus = data_modulus * ZqRistretto::from(coeff_modulus[0].value());
} else {
for modulus in coeff_modulus.iter().take(coeff_modulus.len() - 1) {
data_modulus = data_modulus * ZqRistretto::from(modulus.value());
}
}
// Generate encryption parameters and encrypt/decrypt functions.
let params = BfvEncryptionParametersBuilder::new()
.set_poly_modulus_degree(degree)
.set_coefficient_modulus(coeff_modulus.clone())
.set_plain_modulus(plain_modulus.clone())
.set_coefficient_modulus(coeff_modulus)
.set_plain_modulus(plain_modulus)
.build()
.unwrap();
@@ -227,198 +67,25 @@ where
let decryptor = Decryptor::new(&ctx, &secret_key).unwrap();
// Generate plaintext data
let (plaintext, ciphertext, u_exported, e_exported, r_exported) = if batch_encoder {
let encoder = BFVEncoder::new(&ctx).unwrap();
let mut data = vec![0; encoder.get_slot_count()];
let encoder = BFVScalarEncoder::new();
// Generate plaintext data
let plaintext = encoder.encode_unsigned(message).unwrap();
data[0] = message;
let plaintext = encoder.encode_unsigned(&data).unwrap();
let (ciphertext, u, e, r) = encryptor.encrypt_return_components(&plaintext).unwrap();
// Generate an encrypted message with components
let (ciphertext, u_exported, e_exported, r_exported) = encryptor
// .encrypt_return_components(&plaintext, true, None)
.encrypt_return_components(&plaintext)
.unwrap();
let decrypted = decryptor.decrypt(&ciphertext).unwrap();
let data = encoder.decode_unsigned(&decrypted).unwrap();
assert_eq!(message, data, "decryption failed.");
// Assert that the decryption is correct. If this fails then there is no
// reason to perform the matrix proof.
let decrypted = decryptor.decrypt(&ciphertext).unwrap();
let data_2 = encoder.decode_unsigned(&decrypted).unwrap();
assert_eq!(data, data_2, "decryption failed.");
(plaintext, ciphertext, u_exported, e_exported, r_exported)
} else {
let encoder = BFVScalarEncoder::new();
// Generate plaintext data
let plaintext = encoder.encode_unsigned(message).unwrap();
let (ciphertext, u_exported, e_exported, r_exported) =
encryptor.encrypt_return_components(&plaintext).unwrap();
let decrypted = decryptor.decrypt(&ciphertext).unwrap();
let data = encoder.decode_unsigned(&decrypted).unwrap();
assert_eq!(message, data, "decryption failed.");
(plaintext, ciphertext, u_exported, e_exported, r_exported)
let message = BfvMessage {
plaintext,
bounds: None,
};
// Convert all components into their polynomial representations in the
// fields we use in this package.
let m = Polynomial {
coeffs: strip_trailing_value(
(0..plaintext.len())
.map(|i| Zq::from(plaintext.get_coefficient(i)))
.collect::<Vec<_>>(),
Zq::zero(),
),
let statement = BfvProofStatement::PublicKeyEncryption {
message_id: 0,
ciphertext,
public_key: Cow::Borrowed(&public_key),
};
let u = convert_to_polynomial(u_exported.clone()).pop().unwrap();
let u_small = convert_to_smallint(&coeff_modulus, u_exported.clone());
let mut es = convert_to_polynomial(e_exported.clone());
let e_1 = es.remove(0);
let e_2 = es.remove(0);
let e_small = convert_to_smallint(&coeff_modulus, e_exported.clone());
let mut cs =
convert_to_polynomial(PolynomialArray::new_from_ciphertext(&ctx, &ciphertext).unwrap());
let c_0 = cs.remove(0);
let c_1 = cs.remove(0);
let mut pk =
convert_to_polynomial(PolynomialArray::new_from_public_key(&ctx, &public_key).unwrap());
let p_0 = pk.remove(0);
let p_1 = pk.remove(0);
let r_coeffs = (0..r_exported.len())
.map(|i| r_exported.get_coefficient(i))
.collect::<Vec<u64>>();
let r = Polynomial {
coeffs: r_coeffs
.iter()
.map(|r_i| Zq::from(*r_i))
.collect::<Vec<_>>(),
};
// Delta is the constant polynomial with floor (q/t) as it's DC compopnent.
let delta_dc = bfv_delta(data_modulus, plain_modulus.value());
let delta_dc = Zq::try_from(delta_dc).unwrap();
let delta = Polynomial {
coeffs: vec![delta_dc],
};
// Set up the BFV equations.
let one = make_poly(&[1]);
let zero = make_poly(&[]);
let a = Matrix::<Polynomial<_>>::from([
[
delta.clone(),
one.clone(),
p_0.clone(),
one.clone(),
zero.clone(),
],
[
zero.clone(),
zero.clone(),
p_1.clone(),
zero.clone(),
one.clone(),
],
]);
let s = Matrix::<Polynomial<_>>::from([
[m.clone()],
[r.clone()],
[u.clone()],
[e_1.clone()],
[e_2.clone()],
]);
// Set up the field polymonial divisor (x^N + 1).
let mut f_components = vec![0; (degree + 1) as usize];
f_components[0] = 1;
f_components[degree as usize] = 1;
let f = make_poly(&f_components);
// We do this without the polynomial division and then perform that at
// the end.
let mut t = &a * &s;
// Divide back to a polynomial of at max degree `degree`
let t_0 = t[(0, 0)].vartime_div_rem_restricted_rhs(&f).1;
let t_1 = t[(1, 0)].vartime_div_rem_restricted_rhs(&f).1;
t[(0, 0)] = t_0;
t[(1, 0)] = t_1;
// Test that our equations match the matrix result.
let t_0_from_eq = (delta * &m).vartime_div_rem_restricted_rhs(&f).1
+ r.clone()
+ (p_0 * &u).vartime_div_rem_restricted_rhs(&f).1
+ e_1;
let t_1_from_eq = (p_1 * &u).vartime_div_rem_restricted_rhs(&f).1 + e_2;
// Assertions that the SEAL ciphertext matches our calculated one. We
// use panics here to avoid the large printout from assert_eq.
if t[(0, 0)] != t_0_from_eq {
panic!("Matrix and written out equation match for t_0");
}
if t[(1, 0)] != t_1_from_eq {
panic!("Matrix and written out equation match for t_1");
}
if t[(0, 0)] != c_0 {
panic!("t_0 and c_0 are not equal");
}
if t[(1, 0)] != c_1 {
panic!("t_1 and c_1 are not equal");
}
// Assert that the equations are equal when written up as a matrix (this
// should trivially pass if the above assertions pass)
assert_eq!(t, Matrix::<Polynomial<_>>::from([[c_0], [c_1]]));
// Calculate bounds for the zero knowledge proof
let mut m_coeffs = (0..plaintext.len())
.map(|i| plaintext.get_coefficient(i) as i64)
.collect::<Vec<i64>>();
m_coeffs.resize(degree as usize, 0);
let mut r_coeffs = (0..r_exported.len())
.map(|i| r_exported.get_coefficient(i) as i64)
.collect::<Vec<i64>>();
r_coeffs.resize(degree as usize, 0);
// Calculate the bounds for each element in S. m and r should have bounds of
// the plaintext modulus, while u is ternary and e is sampled from the
// centered binomial distribution with a standard deviation of 3.2.
let m_bounds = if batch_encoder {
Bounds(vec![plain_modulus.value(); m_coeffs.len()])
} else {
let mut bounds = vec![0; m_coeffs.len()];
bounds[0] = plain_modulus.value();
Bounds(bounds)
};
let r_bounds = Bounds(vec![plain_modulus.value(); r_coeffs.len()]);
let u_bounds = Bounds(vec![2; u_small[0].len()]);
let e1_bounds = Bounds(vec![16; e_small[0].len()]);
let e2_bounds = Bounds(vec![16; e_small[1].len()]);
let s_components_bounds = vec![m_bounds, r_bounds, u_bounds, e1_bounds, e2_bounds];
let b: Matrix<Bounds> = Matrix::from(s_components_bounds);
LatticeProblem { a, s, t, f, b }
let witness = BfvWitness::PublicKeyEncryption { u, e, r };
generate_prover_knowledge(&[statement], &[message], &[witness], &params, &ctx)
}

View File

@@ -2,9 +2,8 @@ use merlin::Transcript;
use logproof::{
rings::{SealQ128_1024, SealQ128_2048, SealQ128_4096, SealQ128_8192},
test::{seal_bfv_encryption_linear_relation, LatticeProblem},
InnerProductVerifierKnowledge, LogProof, LogProofGenerators, LogProofProverKnowledge,
LogProofTranscript,
test::seal_bfv_encryption_linear_relation,
InnerProductVerifierKnowledge, LogProof, LogProofGenerators, LogProofTranscript,
};
use sunscreen_math::ring::BarrettConfig;
@@ -12,10 +11,7 @@ fn zero_knowledge_proof<B, const N: usize>(message: u64, degree: u64, plain_modu
where
B: BarrettConfig<N>,
{
let LatticeProblem { a, s, t, f, b } =
seal_bfv_encryption_linear_relation::<B, N>(message, degree, plain_modulus, true);
let pk = LogProofProverKnowledge::new(&a, &s, &t, &b, &f);
let pk = seal_bfv_encryption_linear_relation::<B, N>(message, degree, plain_modulus);
let mut transcript = Transcript::new(b"test");
let mut verify_transcript = transcript.clone();
@@ -45,15 +41,15 @@ fn zero_knowledge_bfv_proof_1024() {
#[test]
fn full_knowledge_bfv_proof_2048() {
seal_bfv_encryption_linear_relation::<SealQ128_2048, 1>(12, 2048, 1032193, true);
seal_bfv_encryption_linear_relation::<SealQ128_2048, 1>(12, 2048, 1032193);
}
#[test]
fn full_knowledge_bfv_proof_4096() {
seal_bfv_encryption_linear_relation::<SealQ128_4096, 2>(12, 4096, 1032193, true);
seal_bfv_encryption_linear_relation::<SealQ128_4096, 2>(12, 4096, 1032193);
}
#[test]
fn full_knowledge_bfv_proof_8192() {
seal_bfv_encryption_linear_relation::<SealQ128_8192, 3>(12, 8192, 1032193, true);
seal_bfv_encryption_linear_relation::<SealQ128_8192, 3>(12, 8192, 1032193);
}

View File

@@ -24,7 +24,7 @@ features = ["bulletproofs", "linkedproofs"]
# Build docs.rs with these features
[package.metadata.docs.rs]
features = ["bulletproofs", "linkedproofs"]
rustdoc-args = [ "--html-in-header", "katex-header.html" ]
rustdoc-args = ["--html-in-header", "docs/assets/katex-header.html"]
[dependencies]
bumpalo = { workspace = true }

View File

@@ -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");
```

View File

@@ -1,5 +1,6 @@
use crate::fhe::{FheCompile, FheFrontendCompilation};
use crate::params::{determine_params, PlainModulusConstraint};
use crate::zkp::{Linked, NotLinked};
use crate::{
zkp, Application, CallSignature, Error, FheProgramMetadata, Params, RequiredKeys, Result,
SchemeType, SecurityLevel, ZkpProgramFn,
@@ -7,8 +8,11 @@ use crate::{
use std::collections::{HashMap, HashSet};
use std::marker::PhantomData;
use sunscreen_fhe_program::FheProgramTrait;
use sunscreen_runtime::{marker, CompiledFheProgram, Fhe, FheRuntime, FheZkp, Zkp};
use sunscreen_zkp_backend::{CompiledZkpProgram, FieldSpec, ZkpBackend};
use sunscreen_runtime::{
marker, CompiledFheProgram, CompiledZkpProgram, Fhe, FheRuntime, FheZkp, Zkp,
ZkpProgramMetadata,
};
use sunscreen_zkp_backend::{FieldSpec, ZkpBackend};
#[derive(Debug, Clone)]
enum ParamsMode {
@@ -159,14 +163,14 @@ impl<B> Default for ZkpCompilerData<B> {
fn default() -> Self {
Self {
zkp_program_fns: vec![],
linked_zkp_program_fns: vec![],
}
}
}
struct ZkpCompilerData<B> {
// In practice, B should always be BoxZkpFn<Field = F>> where
// F: FieldSpec.
zkp_program_fns: Vec<B>,
zkp_program_fns: Vec<Box<dyn ZkpProgramFn<B, Link = NotLinked>>>,
linked_zkp_program_fns: Vec<Box<dyn ZkpProgramFn<B, Link = Linked>>>,
}
enum CompilerData<B> {
@@ -238,11 +242,22 @@ impl<B> CompilerData<B> {
}
}
type BoxZkpFn<F> = Box<dyn ZkpProgramFn<F>>;
/**
* A frontend compiler for Sunscreen FHE programs.
*/
/// A compiler for Sunscreen FHE programs.
//
// A note for developer sanity below: the type strategy here is to have `.fhe_program` enhance to
// an FHE-capable compiler, and `.zkp_backend` to enhance to a ZKP-capable compiler. For that
// strategy alone, the only methods that need to be defined in multiple impls are the
// aforementioned ones, as the returned compiler type will change depending on the current compiler
// type.
//
// But there's one added complication in that the ZKP method `.zkp_program` has type restrictions
// depending on if the current ZKP-capable compiler is also FHE-capable, so that method also needs
// multiple definitions.
//
// TODO: allow FHE params to be specified for both FHE and ZKP programs. This is
// surprisingly complicated with the current approach. We may have to split into two markers (one
// for FHE and one for ZKP) for that to be feasible, without requiring 6 definitions for the same
// method.
pub struct GenericCompiler<T, B> {
data: CompilerData<B>,
_phantom: PhantomData<T>,
@@ -296,7 +311,52 @@ impl Compiler {
}
}
impl<T, B> GenericCompiler<T, B> {
// This generic impl can contain public methods where the builder remains the same type, or
// internal methods that are restricted to FHE-capable builder types.
impl<T: marker::Fhe, B> GenericCompiler<T, B> {
/**
* Set the compiler to search for suitable encryption scheme parameters for the FHE program.
*/
pub fn find_params(mut self) -> Self {
self.data.fhe_data_mut().params_mode = ParamsMode::Search;
self
}
/**
* Set the constraint the parameter search algorithm places on the plaintext modulus.
* You can either force the algorithm to use an exact value or any value that supports
* batching of at least n bits in length.
*/
pub fn plain_modulus_constraint(mut self, p: PlainModulusConstraint) -> Self {
self.data.fhe_data_mut().plain_modulus_constraint = p;
self
}
/**
* Don't use the parameter search algorithm, and instead explicitly set the scheme's parameters.
* For expert use and may cause failures.
*/
pub fn with_params(mut self, params: &Params) -> Self {
self.data.fhe_data_mut().params_mode = ParamsMode::Manual(params.clone());
self
}
/**
* Set the security level. If unspecified, the compiler assumes 128-bit security.
*/
pub fn security_level(mut self, security_level: SecurityLevel) -> Self {
self.data.fhe_data_mut().security_level = security_level;
self
}
/**
* The minimum number of bits of noise budget the search algorithm will leave for all outputs.
*/
pub fn additional_noise_budget(mut self, noise_margin: u32) -> Self {
self.data.fhe_data_mut().noise_margin = noise_margin;
self
}
fn compile_fhe(&self) -> Result<HashMap<String, CompiledFheProgram>> {
let fhe_data: &FheCompilerData = self.data.fhe_data();
@@ -398,29 +458,121 @@ impl<T, B> GenericCompiler<T, B> {
}
}
impl<T, B> GenericCompiler<T, BoxZkpFn<B>>
where
B: FieldSpec,
{
fn compile_zkp(&self) -> Result<HashMap<String, CompiledZkpProgram>> {
// This generic impl can contain public methods where the builder remains the same type, or
// internal methods that are restricted to ZKP-capable builder types.
impl<T: marker::Zkp, B: FieldSpec> GenericCompiler<T, B> {
fn compile_zkp(&self, params: Option<&Params>) -> Result<HashMap<String, CompiledZkpProgram>> {
let zkp_data = self.data.zkp_data();
let linked_zkp_programs = zkp_data.linked_zkp_program_fns.iter().map(|prog| {
// As long as we've properly maintained invariants internal to the compiler, linked
// programs should only be present when params are available.
let params = params.expect("no params; please file a bug!").clone();
// Note: this is currently unsupported because determining the exact arbitrary
// plaintext modulus from the dynamic length of a linked ZKP input in general is not
// possible (as x.ilog2() is not injective). We may support this in the future.
if !params.plain_modulus.is_power_of_two() {
return Err(Error::unsupported(
"Plaintext modulus must be a power of two for ZKP programs with #[linked] arguments.",
));
}
let result = prog.build(params.plain_modulus)?;
let result = zkp::compile(&result);
let metadata = ZkpProgramMetadata {
params: Some(params),
signature: prog.signature(),
};
let compiled_program = CompiledZkpProgram {
zkp_program_fn: result,
metadata,
};
Ok((prog.name().to_owned(), compiled_program))
});
let zkp_programs = zkp_data
.zkp_program_fns
.iter()
.map(|prog| {
let result = prog.build()?;
let result = prog.build(())?;
let result = zkp::compile(&result);
let metadata = ZkpProgramMetadata {
params: None,
signature: prog.signature(),
};
let compiled_program = CompiledZkpProgram {
zkp_program_fn: result,
metadata,
};
Ok((prog.name().to_owned(), result))
Ok((prog.name().to_owned(), compiled_program))
})
.chain(linked_zkp_programs)
.collect::<Result<HashMap<_, _>>>()?;
Ok(zkp_programs)
}
}
fn compile_internal(self) -> Result<Application<T>> {
Application::new(HashMap::new(), self.compile_zkp()?)
impl<B> ZkpCompiler<B>
where
B: FieldSpec,
{
/// Add the given FHE program for compilation.
pub fn fhe_program<F>(self, fhe_program_fn: F) -> FheZkpCompiler<B>
where
F: FheProgramFn + 'static,
{
let mut fhe_data = FheCompilerData::default();
fhe_data.fhe_program_fns.push(Box::new(fhe_program_fn));
FheZkpCompiler {
data: CompilerData::new_fhe_zkp(fhe_data, self.data.unwrap_zkp()),
_phantom: PhantomData,
}
}
/// Add the given ZKP program for compilation.
///
/// Note that this method will not accept "linked" ZKP programs, which have inputs linked from
/// FHE programs. You must first call `fhe_program` to add an FHE program to unlock this
/// capability.
///
/// The following will fail to compile:
/// ```compile_fail
/// # use sunscreen::{bulletproofs::BulletproofsBackend, types::zkp::{BfvSigned, BulletproofsField, Field, FieldSpec}, zkp_program, zkp_var, Compiler };
///
/// #[zkp_program]
/// fn prog<F: FieldSpec>(#[linked] x: BfvSigned<F>) { }
///
/// let app = Compiler::new()
/// .zkp_backend::<BulletproofsBackend>()
/// .zkp_program(prog)
/// .compile()
/// .unwrap();
/// ```
pub fn zkp_program<F>(mut self, zkp_program_fn: F) -> Self
where
F: ZkpProgramFn<B, Link = zkp::NotLinked> + 'static,
{
self.data
.zkp_data_mut()
.zkp_program_fns
.push(Box::new(zkp_program_fn));
self
}
/**
* Compile the ZKP programs. If successful, returns an
* [`Application`] containing a compiled form of each
* `zkp_program` argument.
*
* # Remarks
* Each specified ZKP program must have a unique name,
* regardless of its parent module or crate. `compile` returns
* a [`Error::NameCollision`] if two or more ZKP programs
* have the same name.
*/
pub fn compile(self) -> Result<Application<Zkp>> {
Application::new(HashMap::new(), self.compile_zkp(None)?)
}
}
@@ -428,7 +580,7 @@ impl FheCompiler {
/**
* Add the given FHE program for compilation.
*/
pub fn fhe_program<F>(mut self, fhe_program_fn: F) -> FheCompiler
pub fn fhe_program<F>(mut self, fhe_program_fn: F) -> Self
where
F: FheProgramFn + 'static,
{
@@ -442,7 +594,7 @@ impl FheCompiler {
/**
* Set a ZKP backend for compiling ZKP programs.
*/
pub fn zkp_backend<B: ZkpBackend>(self) -> FheZkpCompiler<BoxZkpFn<B::Field>> {
pub fn zkp_backend<B: ZkpBackend>(self) -> FheZkpCompiler<B::Field> {
let data = CompilerData::new_fhe_zkp(self.data.unwrap_fhe(), ZkpCompilerData::default());
FheZkpCompiler {
@@ -474,56 +626,6 @@ impl FheCompiler {
}
}
impl<B> ZkpCompiler<B>
where
B: FieldSpec,
{
/**
* Add the given FHE program for compilation.
*/
pub fn fhe_program<F>(self, fhe_program_fn: F) -> FheZkpCompiler<B>
where
F: FheProgramFn + 'static,
{
let mut fhe_data = FheCompilerData::default();
fhe_data.fhe_program_fns.push(Box::new(fhe_program_fn));
FheZkpCompiler {
data: CompilerData::new_fhe_zkp(fhe_data, self.data.unwrap_zkp()),
_phantom: PhantomData,
}
}
/**
* Add the given FHE program for compilation.
*/
pub fn zkp_program<F>(mut self, zkp_program_fn: F) -> Self
where
F: ZkpProgramFn<B> + 'static,
{
self.data
.zkp_data_mut()
.zkp_program_fns
.push(Box::new(zkp_program_fn));
self
}
/**
* Compile the ZKP programs. If successful, returns an
* [`Application`] containing a compiled form of each
* `zkp_program` argument.
*
* # Remarks
* Each specified ZKP program must have a unique name,
* regardless of its parent module or crate. `compile` returns
* a [`Error::NameCollision`] if two or more FHE programs
* have the same name.
*/
pub fn compile(self) -> Result<Application<Zkp>> {
self.compile_internal()
}
}
impl<B> FheZkpCompiler<B>
where
B: FieldSpec,
@@ -542,17 +644,35 @@ where
self
}
/**
* Add the given FHE program for compilation.
*/
/// Add the given ZKP program for compilation.
///
/// This method _will_ accept "linked" ZKP programs, which have inputs linked from
/// FHE programs.
///
/// ```
/// # use sunscreen::{bulletproofs::BulletproofsBackend, types::zkp::{BfvSigned, BulletproofsField, Field, FieldSpec}, fhe_program, zkp_program, zkp_var, Compiler };
/// # #[fhe_program(scheme = "bfv")]
/// # fn fhe_prog() { }
///
/// #[zkp_program]
/// fn zkp_prog<F: FieldSpec>(#[linked] x: BfvSigned<F>) { }
///
/// let app = Compiler::new()
/// .fhe_program(fhe_prog)
/// .zkp_backend::<BulletproofsBackend>()
/// .zkp_program(zkp_prog)
/// .compile()
/// .unwrap();
/// ```
pub fn zkp_program<F>(mut self, zkp_program_fn: F) -> Self
where
F: ZkpProgramFn<B> + 'static,
{
self.data
.zkp_data_mut()
.zkp_program_fns
.push(Box::new(zkp_program_fn));
let zkp_data = self.data.zkp_data_mut();
match zkp_program_fn.into_linked() {
Ok(linked) => zkp_data.linked_zkp_program_fns.push(linked),
Err(not_linked) => zkp_data.zkp_program_fns.push(not_linked),
}
self
}
@@ -575,55 +695,16 @@ where
* will return a [`Error::NameCollision`] error.
*/
pub fn compile(self) -> Result<Application<FheZkp>> {
self.compile_internal()
}
}
impl<T, B> GenericCompiler<T, B>
where
T: marker::Fhe,
{
/**
* Set the compiler to search for suitable encryption scheme parameters for the FHE program.
*/
pub fn find_params(mut self) -> Self {
self.data.fhe_data_mut().params_mode = ParamsMode::Search;
self
}
/**
* Set the constraint the parameter search algorithm places on the plaintext modulus.
* You can either force the algorithm to use an exact value or any value that supports
* batching of at least n bits in length.
*/
pub fn plain_modulus_constraint(mut self, p: PlainModulusConstraint) -> Self {
self.data.fhe_data_mut().plain_modulus_constraint = p;
self
}
/**
* Don't use the parameter search algorithm, and instead explicitly set the scheme's parameters.
* For expert use and may cause failures.
*/
pub fn with_params(mut self, params: &Params) -> Self {
self.data.fhe_data_mut().params_mode = ParamsMode::Manual(params.clone());
self
}
/**
* Set the security level. If unspecified, the compiler assumes 128-bit security.
*/
pub fn security_level(mut self, security_level: SecurityLevel) -> Self {
self.data.fhe_data_mut().security_level = security_level;
self
}
/**
* The minimum number of bits of noise budget the search algorithm will leave for all outputs.
*/
pub fn additional_noise_budget(mut self, noise_margin: u32) -> Self {
self.data.fhe_data_mut().noise_margin = noise_margin;
self
let fhe_programs = self.compile_fhe()?;
let params = fhe_programs
.values()
.next()
.map(|p| &p.metadata.params)
.or_else(|| match &self.data.fhe_data().params_mode {
ParamsMode::Search => None,
ParamsMode::Manual(p) => Some(p),
});
Application::new(self.compile_fhe()?, self.compile_zkp(params)?)
}
}
@@ -634,8 +715,8 @@ where
*/
pub type Compiler = GenericCompiler<(), ()>;
pub type FheCompiler = GenericCompiler<Fhe, ()>;
pub type ZkpCompiler<F> = GenericCompiler<Zkp, BoxZkpFn<F>>;
pub type FheZkpCompiler<F> = GenericCompiler<FheZkp, BoxZkpFn<F>>;
pub type ZkpCompiler<B> = GenericCompiler<Zkp, B>;
pub type FheZkpCompiler<B> = GenericCompiler<FheZkp, B>;
#[cfg(test)]
mod tests {
@@ -702,6 +783,8 @@ mod tests {
let app = Compiler::new().fhe_program(kitty).compile().unwrap();
assert_eq!(app.type_id(), TypeId::of::<Application<Fhe>>());
assert_eq!(app.fhe_programs.len(), 1);
assert_eq!(app.zkp_programs.len(), 0);
}
#[test]
@@ -716,6 +799,8 @@ mod tests {
.unwrap();
assert_eq!(app.type_id(), TypeId::of::<Application<Zkp>>());
assert_eq!(app.fhe_programs.len(), 0);
assert_eq!(app.zkp_programs.len(), 1);
}
#[test]
@@ -734,5 +819,7 @@ mod tests {
.unwrap();
assert_eq!(app.type_id(), TypeId::of::<Application<FheZkp>>());
assert_eq!(app.fhe_programs.len(), 1);
assert_eq!(app.zkp_programs.len(), 1);
}
}

View File

@@ -93,4 +93,4 @@ impl Error {
/**
* Wrapper around [`Result`](std::result::Result) with this crate's error type.
*/
pub type Result<T> = std::result::Result<T, Error>;
pub type Result<T, E = Error> = std::result::Result<T, E>;

View File

@@ -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;
@@ -55,7 +54,6 @@ use fhe::{FheOperation, Literal};
use petgraph::stable_graph::StableGraph;
use serde::{Deserialize, Serialize};
use sunscreen_runtime::{marker, Fhe, FheZkp, Zkp};
use sunscreen_zkp_backend::CompiledZkpProgram;
use std::cell::RefCell;
use std::collections::HashMap;
@@ -68,10 +66,10 @@ pub use seal_fhe::Plaintext as SealPlaintext;
pub use sunscreen_compiler_macros::*;
pub use sunscreen_fhe_program::{SchemeType, SecurityLevel};
pub use sunscreen_runtime::{
CallSignature, Ciphertext, CompiledFheProgram, Error as RuntimeError, FheProgramInput,
FheProgramInputTrait, FheProgramMetadata, FheRuntime, FheZkpRuntime, InnerCiphertext,
InnerPlaintext, Params, Plaintext, PrivateKey, ProofBuilder, PublicKey, RequiredKeys, Runtime,
VerificationBuilder, WithContext, ZkpProgramInput, ZkpRuntime,
CallSignature, Ciphertext, CompiledFheProgram, CompiledZkpProgram, Error as RuntimeError,
FheProgramInput, FheProgramInputTrait, FheProgramMetadata, FheRuntime, FheZkpRuntime,
InnerCiphertext, InnerPlaintext, Params, Plaintext, PrivateKey, ProofBuilder, PublicKey,
RequiredKeys, Runtime, VerificationBuilder, WithContext, ZkpProgramInput, ZkpRuntime,
};
#[cfg(feature = "bulletproofs")]
pub use sunscreen_zkp_backend::bulletproofs;

27
sunscreen/src/linked.rs Normal file
View 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};

View File

@@ -36,6 +36,22 @@ impl NumCiphertexts for Signed {
const NUM_CIPHERTEXTS: usize = 1;
}
#[cfg(feature = "linkedproofs")]
mod sharing {
use super::*;
use crate::types::zkp::BfvSigned;
use sunscreen_runtime::LinkWithZkp;
use sunscreen_zkp_backend::FieldSpec;
impl LinkWithZkp for Signed {
type ZkpType<F: FieldSpec> = BfvSigned<F>;
/// While a freshly encoded plaintext will only use up to 64 coefficients, plaintexts resulting
/// from a multiplicative circuit can result in valid Signed encodings that use more than 64
/// coefficients. A bound of 128 should work for virtually all cases.
const DEGREE_BOUND: usize = 128;
}
}
impl FheProgramInputTrait for Signed {}
impl FheType for Signed {}
impl BfvType for Signed {}

View File

@@ -0,0 +1,269 @@
use sunscreen_compiler_macros::TypeName;
use sunscreen_runtime::LinkWithZkp;
use sunscreen_zkp_backend::{BigInt, FieldSpec};
use crate::{
invoke_gadget,
types::{bfv::Signed, zkp::ProgramNode},
zkp::{with_zkp_ctx, ZkpContextOps},
};
use super::{gadgets::SignedModulus, DynamicNumFieldElements, Field, ToNativeFields};
use crate as sunscreen;
/// A BFV plaintext polynomial that has been linked to a ZKP program.
///
/// Here `N` is the degree of the _linked_ polynomial, which may be less than the full lattice
/// dimension. See [`Link::DEGREE_BOUND`](sunscreen_runtime::Link).
#[derive(Debug, Clone, TypeName)]
struct BfvPlaintext<F: FieldSpec, const N: usize> {
data: Vec<Field<F>>,
}
impl<F: FieldSpec, const N: usize> DynamicNumFieldElements for BfvPlaintext<F, N> {
fn num_native_field_elements(plaintext_modulus: u64) -> usize {
let log_p = plaintext_modulus.ilog2() + 1;
log_p as usize * N
}
}
impl<F: FieldSpec, const N: usize> ToNativeFields for BfvPlaintext<F, N> {
fn to_native_fields(&self) -> Vec<BigInt> {
self.data.iter().map(|x| x.val).collect()
}
}
#[derive(Debug, Clone, TypeName)]
/// A [BFV signed integer](crate::types::bfv::Signed) that has been linked to a ZKP program.
///
/// Use the [`AsFieldElement::into_field_elem`] method to decode the the value into a field
/// element within a ZKP program.
pub struct BfvSigned<F: FieldSpec>(BfvPlaintext<F, { <Signed as LinkWithZkp>::DEGREE_BOUND }>);
impl<F: FieldSpec> DynamicNumFieldElements for BfvSigned<F> {
fn num_native_field_elements(plaintext_modulus: u64) -> usize {
<BfvPlaintext<F, { <Signed as LinkWithZkp>::DEGREE_BOUND }> as DynamicNumFieldElements>::
num_native_field_elements(plaintext_modulus)
}
}
impl<F: FieldSpec> ToNativeFields for BfvSigned<F> {
fn to_native_fields(&self) -> Vec<BigInt> {
self.0.to_native_fields()
}
}
/// Decode the underlying plaintext polynomial into the field.
pub trait AsFieldElement<F: FieldSpec> {
/// Get the plaintext value as a field element.
fn into_field_elem(self) -> ProgramNode<Field<F>>;
}
impl<F: FieldSpec> AsFieldElement<F> for ProgramNode<BfvSigned<F>> {
fn into_field_elem(self) -> ProgramNode<Field<F>> {
let bound = self.ids.len() / <Signed as LinkWithZkp>::DEGREE_BOUND;
let plain_modulus = 2u64.pow(bound as u32 - 1);
let (plain_modulus, plain_modulus_1, two, mut coeffs) = with_zkp_ctx(|ctx| {
let plain_modulus = ctx.add_constant(&BigInt::from(plain_modulus));
let one = ctx.add_constant(&BigInt::from_u32(1));
let two = ctx.add_constant(&BigInt::from_u32(2));
let plain_modulus_1 = ctx.add_addition(plain_modulus, one);
// Get coeffs via 2s complement construction
let coeffs = self
.ids
.chunks(bound)
.map(|xs| {
let mut c = ctx.add_constant(&BigInt::ZERO);
for (i, x) in xs.iter().enumerate() {
let pow = ctx.add_constant(&(BigInt::ONE << i));
let mul = ctx.add_multiplication(pow, *x);
if i == bound - 1 {
c = ctx.add_subtraction(c, mul);
} else {
c = ctx.add_addition(c, mul);
}
}
c
})
.collect::<Vec<_>>();
(plain_modulus, plain_modulus_1, two, coeffs)
});
// Translate coefficients into field modulus
let cutoff_divider = SignedModulus::new(F::FIELD_MODULUS, 1);
let divider = SignedModulus::new(F::FIELD_MODULUS, 63);
let neg_cutoff = invoke_gadget(cutoff_divider, &[plain_modulus_1, two])[0];
for c in coeffs.iter_mut() {
let is_negative = invoke_gadget(divider, &[*c, neg_cutoff])[0];
*c = with_zkp_ctx(|ctx| {
let shift = ctx.add_multiplication(plain_modulus, is_negative);
ctx.add_subtraction(*c, shift)
});
}
ProgramNode::new(&[with_zkp_ctx(|ctx| {
// Get signed value by decoding according to Signed implementation.
let mut x = ctx.add_constant(&BigInt::ZERO);
for (i, c) in coeffs.iter().enumerate() {
let pow = ctx.add_constant(&(BigInt::ONE << i));
let mul = ctx.add_multiplication(pow, *c);
x = ctx.add_addition(x, mul);
}
x
})])
}
}
#[cfg(test)]
mod tests {
use super::*;
use sunscreen_runtime::ZkpRuntime;
use sunscreen_zkp_backend::bulletproofs::BulletproofsBackend;
use sunscreen_zkp_backend::FieldSpec;
use crate::types::zkp::{BulletproofsField, Field};
use crate::{self as sunscreen, Compiler, PlainModulusConstraint};
use crate::{fhe_program, zkp_program};
#[zkp_program]
fn is_eq<F: FieldSpec>(#[linked] x: BfvSigned<F>, #[public] y: Field<F>) {
x.into_field_elem().constrain_eq(y);
}
#[fhe_program(scheme = "bfv")]
fn doggie() {}
#[test]
#[ignore = "currently unsupported"]
fn can_decode_signed_for_arbitrary_plain_moduli() {
test_plain_modulus(4095);
test_plain_modulus(4097);
test_plain_modulus(1153);
}
#[test]
fn can_decode_signed_for_powers_of_two_moduli() {
test_plain_modulus(1024);
test_plain_modulus(4096);
test_plain_modulus(262_144); // default in compiler.rs
}
#[test]
fn can_decode_signed_with_coefficients_near_cutoff() {
let plain_modulus = 1024_u64;
let is_eq_zkp = Compiler::new()
.fhe_program(doggie)
.plain_modulus_constraint(PlainModulusConstraint::Raw(1024))
.zkp_backend::<BulletproofsBackend>()
.zkp_program(is_eq)
.compile()
.unwrap()
.take_zkp_program(is_eq)
.unwrap();
let runtime = ZkpRuntime::new(BulletproofsBackend::new()).unwrap();
let log_p = plain_modulus.ilog2() as usize + 1;
for (coeff, equiv) in [(511, 511), (512, -512), (513, -511)] {
let mut signed_encoding = [0; <Signed as LinkWithZkp>::DEGREE_BOUND];
signed_encoding[0] = coeff;
let coeffs = signed_encoding.map(|c| {
let mut bits = vec![0; log_p];
for (i, b) in bits.iter_mut().enumerate() {
let bit = (c & (0x1 << i)) >> i;
*b = bit;
}
bits
});
let encoded = BfvSigned(BfvPlaintext {
data: coeffs
.into_iter()
.flat_map(|c| c.into_iter().map(BulletproofsField::from))
.collect(),
});
let proof = runtime
.proof_builder(&is_eq_zkp)
.private_input(encoded)
.public_input(BulletproofsField::from(equiv))
.prove()
.unwrap();
runtime
.verification_builder(&is_eq_zkp)
.proof(&proof)
.public_input(BulletproofsField::from(equiv))
.verify()
.unwrap();
}
}
fn test_plain_modulus(plain_modulus: u64) {
let is_eq_zkp = Compiler::new()
.fhe_program(doggie)
.plain_modulus_constraint(PlainModulusConstraint::Raw(plain_modulus))
.zkp_backend::<BulletproofsBackend>()
.zkp_program(is_eq)
.compile()
.unwrap()
.take_zkp_program(is_eq)
.unwrap();
let runtime = ZkpRuntime::new(BulletproofsBackend::new()).unwrap();
let log_p = plain_modulus.ilog2() as usize + 1;
for val in [3i64, -3] {
// Simulate the polynomial signed encoding
let mut signed_encoding = [0; <Signed as LinkWithZkp>::DEGREE_BOUND];
let abs_val = val.unsigned_abs();
for (i, c) in signed_encoding.iter_mut().take(64).enumerate() {
let bit = (abs_val & 0x1 << i) >> i;
*c = if val.is_negative() {
bit * (plain_modulus - bit)
} else {
bit
};
}
// Now further break down each coeff into 2s complement
// Note that these numbers will virtually always be positive in the Zq context, for all but
// pathological plaintext moduli.
let coeffs = signed_encoding.map(|c| {
let mut bits = vec![0; log_p];
for (i, b) in bits.iter_mut().enumerate() {
let bit = (c & (0x1 << i)) >> i;
*b = bit;
}
bits
});
let encoded = BfvSigned(BfvPlaintext {
data: coeffs
.into_iter()
.flat_map(|c| c.into_iter().map(BulletproofsField::from))
.collect(),
});
let proof = runtime
.proof_builder(&is_eq_zkp)
.private_input(encoded)
.public_input(BulletproofsField::from(val))
.prove()
.unwrap();
runtime
.verification_builder(&is_eq_zkp)
.proof(&proof)
.public_input(BulletproofsField::from(val))
.verify()
.unwrap();
}
}
}

View File

@@ -6,6 +6,7 @@ use crate::zkp::{invoke_gadget, with_zkp_ctx, ZkpContextOps};
use super::ToUInt;
#[derive(Clone, Copy)]
pub struct SignedModulus {
field_modulus: BigInt,
max_remainder_bits: usize,

View File

@@ -1,12 +1,13 @@
#[cfg(feature = "linkedproofs")]
mod bfv_plaintext;
mod field;
mod gadgets;
mod program_node;
mod rns_polynomial;
#[cfg(feature = "linkedproofs")]
pub use bfv_plaintext::*;
pub use field::*;
// N.B. `NodeIndex` is actually common to both FHE and ZKP, but it's really only leaked as an
// implementation detail on the ZKP side (via gadgets). So I think it makes sense to export under
// sunscreen::types::zkp.
pub use petgraph::stable_graph::NodeIndex;
pub use program_node::*;
pub use rns_polynomial::*;
@@ -195,9 +196,21 @@ pub trait NumFieldElements {
* programs.
*/
pub trait ZkpType: NumFieldElements + Sized + TypeName + ToNativeFields {}
impl<T: NumFieldElements + Sized + TypeName + ToNativeFields> ZkpType for T {}
/// The dynamic number of native field elements needed to represent a ZKP type.
pub trait DynamicNumFieldElements {
/// Calculate the number of native field elements needed to represent this type.
fn num_native_field_elements(plaintext_modulus: u64) -> usize;
}
/**
* Encapsulates the traits required for an SDLP-linked type to be used in ZKP
* programs.
*/
pub trait LinkedZkpType: DynamicNumFieldElements + Sized + TypeName + ToNativeFields {}
impl<T: DynamicNumFieldElements + Sized + TypeName + ToNativeFields> LinkedZkpType for T {}
/**
* Methods for coercing ZKP data types.
*/

View File

@@ -12,7 +12,7 @@ use crate::{
INDEX_ARENA,
};
use super::{ConstrainCmpVarVar, ConstrainEqVarVar, Field};
use super::{ConstrainCmpVarVar, ConstrainEqVarVar, Field, LinkedZkpType};
#[derive(Clone, Copy)]
/**
@@ -26,10 +26,7 @@ use super::{ConstrainCmpVarVar, ConstrainEqVarVar, Field};
* # Remarks
* For internal use only.
*/
pub struct ProgramNode<T>
where
T: ZkpType,
{
pub struct ProgramNode<T> {
/**
* The indices in the graph that compose the type backing this
* `ProgramNode`.
@@ -38,7 +35,7 @@ where
_phantom: PhantomData<T>,
}
impl<T: ZkpType> std::fmt::Debug for ProgramNode<T> {
impl<T> std::fmt::Debug for ProgramNode<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ProgramNode<elided>")
}
@@ -73,10 +70,13 @@ pub trait CreateZkpProgramInput {
fn constant_input() -> Self;
}
impl<T> ProgramNode<T>
where
T: ZkpType,
{
/// Trait for adding FHE-linked inputs to a ZKP program.
pub trait CreateLinkedZkpProgramInput {
/// Creates an FHE linked program input of type T, which requires the plaintext modulus.
fn linked_input(plaintext_modulus: u64) -> Self;
}
impl<T> ProgramNode<T> {
/**
* Create a new Program node from the given indicies in the
*/
@@ -88,7 +88,7 @@ where
ids_dest.copy_from_slice(ids);
// The memory in the bump allocator is valid until we call reset, which
// we do after creating the FHE program. At this time, no FheProgramNodes should
// we do after creating the ZKP program. At this time, no ZKP ProgramNodes should
// remain.
// We invoke the dark transmutation ritual to turn a finite lifetime into a 'static.
Self {
@@ -104,27 +104,24 @@ where
T: CreateZkpProgramInput + Copy,
{
fn constant_input() -> Self {
(0..N)
.map(|_| T::constant_input())
.collect::<Vec<_>>()
.try_into()
.unwrap_or_else(|_| unreachable!())
[0; N].map(|_| T::constant_input())
}
fn private_input() -> Self {
(0..N)
.map(|_| T::private_input())
.collect::<Vec<_>>()
.try_into()
.unwrap_or_else(|_| unreachable!())
[0; N].map(|_| T::private_input())
}
fn public_input() -> Self {
(0..N)
.map(|_| T::public_input())
.collect::<Vec<_>>()
.try_into()
.unwrap_or_else(|_| unreachable!())
[0; N].map(|_| T::public_input())
}
}
impl<T, const N: usize> CreateLinkedZkpProgramInput for [T; N]
where
T: CreateLinkedZkpProgramInput + Copy,
{
fn linked_input(plaintext_modulus: u64) -> Self {
[0; N].map(|_| T::linked_input(plaintext_modulus))
}
}
@@ -163,6 +160,22 @@ where
}
}
impl<T> CreateLinkedZkpProgramInput for ProgramNode<T>
where
T: LinkedZkpType,
{
fn linked_input(plaintext_modulus: u64) -> Self {
let len = T::num_native_field_elements(plaintext_modulus);
let mut ids = Vec::with_capacity(len);
for _ in 0..len {
ids.push(with_zkp_ctx(|ctx| ctx.add_private_input()));
}
Self::new(&ids)
}
}
impl<T> Add<ProgramNode<T>> for ProgramNode<T>
where
T: AddVar + ZkpType,

View File

@@ -12,16 +12,49 @@ use std::sync::Arc;
use std::vec;
use std::{any::Any, cell::RefCell};
mod sealed {
pub trait Sealed {}
impl Sealed for super::Linked {}
impl Sealed for super::NotLinked {}
}
/// Restrict the associated type [link indicator](`ZkpProgramFn::Link`) on to one of these values.
pub trait Link: sealed::Sealed {
/// The input type that is necessary for this type of ZKP program.
type Input: Clone;
}
/// Indicates that a ZKP program contains inputs from linked FHE programs.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Linked;
impl Link for Linked {
type Input = u64;
}
/// Indicates that a ZKP program does not contain linked inputs.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct NotLinked;
impl Link for NotLinked {
type Input = ();
}
type BoxedFn<F, S> = Box<dyn ZkpProgramFn<F, Link = S>>;
/// An internal representation of a ZKP program specification.
pub trait ZkpProgramFn<F: FieldSpec> {
/// Indicates whether or not this ZKP program contains linked inputs.
type Link: Link;
/// Create a circuit from this specification.
fn build(&self) -> Result<ZkpFrontendCompilation>;
fn build(&self, linked_input: <Self::Link as Link>::Input) -> Result<ZkpFrontendCompilation>;
/// Gets the call signature for this program.
fn signature(&self) -> CallSignature;
/// Gets the name of this program.
fn name(&self) -> &str;
/// Runtime check if this ZKP program fn is linked.
fn into_linked(self) -> Result<BoxedFn<F, Linked>, BoxedFn<F, NotLinked>>;
}
/// An extension of [`ZkpProgramFn`], providing helpers and convenience methods.
@@ -75,9 +108,9 @@ pub trait ZkpProgramFnExt {
/// # Ok(())
/// # }
/// ```
fn compile<B: ZkpBackend>(&self) -> Result<CompiledZkpProgram>
fn compile<B: ZkpBackend>(&self) -> Result<crate::CompiledZkpProgram>
where
Self: ZkpProgramFn<B::Field>,
Self: ZkpProgramFn<B::Field, Link = NotLinked>,
Self: Sized + Clone + AsRef<str> + 'static,
{
Ok(Compiler::new()
@@ -116,7 +149,7 @@ pub trait ZkpProgramFnExt {
fn runtime_with<B: ZkpBackend>(&self, backend: B) -> Result<ZkpRuntime<B>>
where
B: 'static,
Self: ZkpProgramFn<B::Field>,
Self: ZkpProgramFn<B::Field, Link = NotLinked>,
Self: Sized + Clone + AsRef<str> + 'static,
{
Ok(ZkpRuntime::new(backend)?)
@@ -150,7 +183,7 @@ pub trait ZkpProgramFnExt {
fn runtime<B: ZkpBackend + Default>(&self) -> Result<ZkpRuntime<B>>
where
B: 'static,
Self: ZkpProgramFn<B::Field>,
Self: ZkpProgramFn<B::Field, Link = NotLinked>,
Self: Sized + Clone + AsRef<str> + 'static,
{
self.runtime_with(B::default())

View File

@@ -1,78 +1,77 @@
#[cfg(feature = "linkedproofs")]
mod linked_tests {
use logproof::test::seal_bfv_encryption_linear_relation;
use sunscreen::types::zkp::BulletproofsField;
use sunscreen::Error;
use lazy_static::lazy_static;
use sunscreen::types::bfv::Signed;
use sunscreen::types::zkp::{AsFieldElement, BfvSigned, BulletproofsField};
use sunscreen::PlainModulusConstraint;
use sunscreen::{
types::zkp::{ConstrainCmp, Field, FieldSpec, ProgramNode},
fhe_program,
types::zkp::{ConstrainCmp, Field, FieldSpec},
zkp_program, zkp_var, Compiler,
};
use sunscreen_runtime::LinkedProof;
use logproof::rings::SealQ128_1024;
use sunscreen_fhe_program::SchemeType;
use sunscreen_runtime::{FheZkpRuntime, LogProofBuilder, Params, ZkpProgramInput};
use sunscreen_zkp_backend::bulletproofs::BulletproofsBackend;
/// Convert a twos complement represented signed integer into a field element.
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
lazy_static! {
static ref SMALL_PARAMS: Params = Params {
lattice_dimension: 1024,
coeff_modulus: vec![0x7e00001],
plain_modulus: 4_096,
scheme_type: SchemeType::Bfv,
security_level: sunscreen::SecurityLevel::TC128,
};
}
#[fhe_program(scheme = "bfv")]
fn doggie() {}
#[zkp_program]
fn valid_transaction<F: FieldSpec>(#[private] x: [Field<F>; 15], #[public] balance: Field<F>) {
fn valid_transaction<F: FieldSpec>(#[linked] tx: BfvSigned<F>, #[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);
// Reconstruct tx
let tx_recon = tx.into_field_elem();
// Constraint that x is less than or equal to balance
balance.constrain_ge_bounded(x_recon, 64);
balance.constrain_ge_bounded(tx_recon, 64);
// Constraint that x is greater than or equal to zero
lower_bound.constrain_le_bounded(x_recon, 64);
lower_bound.constrain_le_bounded(tx_recon, 64);
}
#[test]
fn test_validated_transaction_example() -> Result<(), Error> {
fn test_valid_transaction_example() {
let app = Compiler::new()
.fhe_program(doggie)
.with_params(&SMALL_PARAMS)
.zkp_backend::<BulletproofsBackend>()
.zkp_program(valid_transaction)
.compile()?;
// Compile the ZKP program
.compile()
.unwrap();
let rt = FheZkpRuntime::new(app.params(), &BulletproofsBackend::new()).unwrap();
let valid_transaction_zkp = app.get_zkp_program(valid_transaction).unwrap();
let balance = 1u64;
let (public_key, _secret_key) = rt.generate_keys().unwrap();
let balance = 10i64;
// Try valid cases
for k in 0..=balance {
let x = k;
for tx in [5, 10] {
let mut proof_builder = LogProofBuilder::new(&rt);
// 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)];
let (_ct, tx_msg) = proof_builder
.encrypt_and_link(&Signed::from(tx), &public_key)
.unwrap();
println!("Performing linked proof");
let lp = LinkedProof::create(
&sdlp,
&shared_indices,
valid_transaction_zkp,
vec![],
vec![BulletproofsField::from(balance)],
vec![],
)
.unwrap();
let lp = proof_builder
.zkp_program(valid_transaction_zkp)
.unwrap()
.linked_input(&tx_msg)
.public_input(BulletproofsField::from(balance))
.build_linkedproof()
.unwrap();
println!("Linked proof done");
println!("Performing linked verify");
@@ -84,30 +83,207 @@ mod linked_tests {
.expect("Failed to verify linked proof");
println!("Linked verify done");
}
}
// Try an invalid case
{
let x = balance + 1;
#[test]
fn test_invalid_transaction_example() {
let app = Compiler::new()
.fhe_program(doggie)
.with_params(&SMALL_PARAMS)
.zkp_backend::<BulletproofsBackend>()
.zkp_program(valid_transaction)
.compile()
.unwrap();
let rt = FheZkpRuntime::new(app.params(), &BulletproofsBackend::new()).unwrap();
let valid_transaction_zkp = app.get_zkp_program(valid_transaction).unwrap();
// 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)];
let (public_key, _secret_key) = rt.generate_keys().unwrap();
println!("Proof should fail");
let lp = LinkedProof::create(
&sdlp,
&shared_indices,
valid_transaction_zkp,
vec![],
vec![BulletproofsField::from(balance)],
vec![],
);
let balance = 10i64;
for tx in [-1, balance + 1] {
let mut proof_builder = LogProofBuilder::new(&rt);
let (_ct, tx_msg) = proof_builder
.encrypt_and_link(&Signed::from(tx), &public_key)
.unwrap();
proof_builder
.zkp_program(valid_transaction_zkp)
.unwrap()
.linked_input(&tx_msg)
.public_input(BulletproofsField::from(balance));
let lp = proof_builder.build_linkedproof();
assert!(lp.is_err());
}
}
Ok(())
#[zkp_program]
fn is_eq<F: FieldSpec>(#[linked] x: BfvSigned<F>, #[public] y: Field<F>) {
x.into_field_elem().constrain_eq(y);
}
#[test]
fn test_is_eq() {
let app = Compiler::new()
.fhe_program(doggie)
.with_params(&SMALL_PARAMS)
.zkp_backend::<BulletproofsBackend>()
.zkp_program(is_eq)
.compile()
.unwrap();
let rt = FheZkpRuntime::new(app.params(), &BulletproofsBackend::new()).unwrap();
let is_eq_zkp = app.get_zkp_program(is_eq).unwrap();
let (public_key, _secret_key) = rt.generate_keys().unwrap();
for val in [3, 0, -3] {
let mut proof_builder = LogProofBuilder::new(&rt);
let (_ct, val_msg) = proof_builder
.encrypt_and_link(&Signed::from(val), &public_key)
.unwrap();
proof_builder
.zkp_program(is_eq_zkp)
.unwrap()
.linked_input(&val_msg)
.public_input(BulletproofsField::from(val));
let lp = proof_builder.build_linkedproof().unwrap_or_else(|_| {
panic!(
"Failed to encode {} value",
if val.is_positive() {
"positive"
} else {
"negative"
}
)
});
lp.verify(is_eq_zkp, vec![BulletproofsField::from(val)], vec![])
.expect("Failed to verify linked proof");
}
}
#[zkp_program]
fn is_eq_3<F: FieldSpec>(
#[linked] x: BfvSigned<F>,
#[linked] y: BfvSigned<F>,
#[private] z: Field<F>,
) {
let x = x.into_field_elem();
let y = y.into_field_elem();
x.constrain_eq(y);
y.constrain_eq(z);
}
#[test]
fn test_same_msg_proof() {
// proves equivalence of pt x and pt x1 within SDLP
// proves equivalence of pt x, pt y, and field elem z within ZKP
let app = Compiler::new()
.fhe_program(doggie)
.with_params(&SMALL_PARAMS)
.zkp_backend::<BulletproofsBackend>()
.zkp_program(is_eq_3)
.compile()
.unwrap();
let rt = FheZkpRuntime::new(app.params(), &BulletproofsBackend::new()).unwrap();
let is_eq_zkp = app.get_zkp_program(is_eq_3).unwrap();
let (public_key, _secret_key) = rt.generate_keys().unwrap();
for val in [3, 0, -3] {
let mut proof_builder = LogProofBuilder::new(&rt);
let (_ct_x, x_msg) = proof_builder
.encrypt_and_link(&Signed::from(val), &public_key)
.unwrap();
// proves same plaintext within SDLP
let _ct_x1 = proof_builder.encrypt_linked(&x_msg, &public_key).unwrap();
// proves same value within ZKP
let (_ct_y, y_msg) = proof_builder
.encrypt_and_link(&Signed::from(val), &public_key)
.unwrap();
proof_builder
.zkp_program(is_eq_zkp)
.unwrap()
.linked_input(&x_msg)
.linked_input(&y_msg)
.private_input(BulletproofsField::from(val));
let lp = proof_builder.build_linkedproof().unwrap();
lp.verify::<ZkpProgramInput>(is_eq_zkp, vec![], vec![])
.expect("Failed to verify linked proof");
}
}
#[test]
fn compiler_enforces_moduli_pow_2() {
// try to compile zkp program with plain modulus 100
let res = Compiler::new()
.fhe_program(doggie)
.plain_modulus_constraint(PlainModulusConstraint::Raw(100))
.zkp_backend::<BulletproofsBackend>()
.zkp_program(is_eq)
.compile();
assert!(matches!(res, Err(sunscreen::Error::Unsupported { .. })));
}
#[test]
fn builder_enforces_moduli_match() {
// compile zkp program with plain modulus 512
let app = Compiler::new()
.fhe_program(doggie)
.plain_modulus_constraint(PlainModulusConstraint::Raw(512))
.zkp_backend::<BulletproofsBackend>()
.zkp_program(is_eq)
.compile()
.unwrap();
let is_eq_zkp = app.get_zkp_program(is_eq).unwrap();
// but use runtime with modulus 4096
let rt = FheZkpRuntime::new(&SMALL_PARAMS, &BulletproofsBackend::new()).unwrap();
let mut proof_builder = LogProofBuilder::new(&rt);
let res = proof_builder.zkp_program(is_eq_zkp);
assert!(matches!(
res,
Err(sunscreen_runtime::Error::BuilderError { .. })
));
}
#[test]
fn throws_linked_arg_mismatch() {
fn test_case(num_linked_inputs: usize, num_private_inputs: usize) {
let app = Compiler::new()
.fhe_program(doggie)
.with_params(&SMALL_PARAMS)
.zkp_backend::<BulletproofsBackend>()
.zkp_program(is_eq_3)
.compile()
.unwrap();
let rt = FheZkpRuntime::new(app.params(), &BulletproofsBackend::new()).unwrap();
let is_eq_zkp = app.get_zkp_program(is_eq_3).unwrap();
let (public_key, _secret_key) = rt.generate_keys().unwrap();
let mut proof_builder = LogProofBuilder::new(&rt);
proof_builder.zkp_program(is_eq_zkp).unwrap();
for _ in 0..num_linked_inputs {
let (_ct, msg) = proof_builder
.encrypt_and_link(&Signed::from(1), &public_key)
.unwrap();
proof_builder.linked_input(&msg);
}
for _ in 0..num_private_inputs {
proof_builder.private_input(BulletproofsField::from(1));
}
let res = proof_builder.build_linkedproof();
assert!(matches!(
res,
Err(sunscreen::RuntimeError::ArgumentMismatch(_))
));
}
// Missing linked inputs
test_case(0, 1);
// Missing one linked inputs
test_case(1, 1);
// TODO add signed/unsigned mismatch after we impl sharing for unlinked.
}
}

51
sunscreen/tests/sdlp.rs Normal file
View File

@@ -0,0 +1,51 @@
#[cfg(feature = "linkedproofs")]
mod sdlp_tests {
use lazy_static::lazy_static;
use sunscreen::types::bfv::Signed;
use sunscreen_fhe_program::SchemeType;
use sunscreen_runtime::{FheRuntime, LogProofBuilder, Params};
lazy_static! {
static ref SMALL_PARAMS: Params = Params {
lattice_dimension: 1024,
coeff_modulus: vec![0x7e00001],
plain_modulus: 4_096,
scheme_type: SchemeType::Bfv,
security_level: sunscreen::SecurityLevel::TC128,
};
}
#[test]
fn prove_one_asymmetric_statement() {
let rt = FheRuntime::new(&SMALL_PARAMS).unwrap();
let (public_key, _secret_key) = rt.generate_keys().unwrap();
let mut logproof_builder = LogProofBuilder::new(&rt);
let _ct = logproof_builder
.encrypt(&Signed::from(3), &public_key)
.unwrap();
let sdlp = logproof_builder.build_logproof().unwrap();
sdlp.verify().unwrap();
}
#[test]
fn prove_linked_asymmetric_statements() {
let rt = FheRuntime::new(&SMALL_PARAMS).unwrap();
let (public_key, _secret_key) = rt.generate_keys().unwrap();
let mut logproof_builder = LogProofBuilder::new(&rt);
let (_a1, linked_a) = logproof_builder
.encrypt_and_link(&Signed::from(2), &public_key)
.unwrap();
let _a2 = logproof_builder
.encrypt_linked(&linked_a, &public_key)
.unwrap();
let _b = logproof_builder
.encrypt(&Signed::from(3), &public_key)
.unwrap();
let sdlp = logproof_builder.build_logproof().unwrap();
sdlp.verify().unwrap();
}
}

View File

@@ -1,5 +1,5 @@
use sunscreen::{types::zkp::Field, zkp_program, Compiler, Runtime};
use sunscreen_runtime::ZkpProgramInput;
use sunscreen_runtime::{TypeNameInstance, ZkpProgramInput};
use sunscreen_zkp_backend::{bulletproofs::BulletproofsBackend, FieldSpec, ZkpBackend};
type BPField = Field<<BulletproofsBackend as ZkpBackend>::Field>;
@@ -40,7 +40,6 @@ fn can_add_and_mul_native_fields() {
#[test]
fn get_input_mismatch_on_incorrect_args() {
use sunscreen_runtime::Error;
use sunscreen_zkp_backend::Error as ZkpError;
#[zkp_program]
fn add_mul<F: FieldSpec>(a: Field<F>, b: Field<F>) {
@@ -57,12 +56,16 @@ fn get_input_mismatch_on_incorrect_args() {
let program = app.get_zkp_program(add_mul).unwrap();
let result = runtime.prove(program, vec![BPField::from(0u8)], vec![], vec![]);
let arg = BPField::from(0u8);
let result = runtime.prove(program, vec![arg], vec![], vec![]);
assert!(matches!(
result,
Err(Error::ZkpError(ZkpError::InputsMismatch(_)))
));
assert_eq!(
result.err().unwrap(),
Error::ArgumentMismatch(Box::new((
vec![arg.type_name_instance(); 2],
vec![arg.type_name_instance(); 1]
)))
);
}
#[test]
@@ -152,11 +155,16 @@ fn can_declare_array_inputs() {
let program = app.get_zkp_program(in_range).unwrap();
let inputs = (0..64u64)
.flat_map(|i| (0..9u64).map(|j| BPField::from(i + j)).collect::<Vec<_>>())
.collect::<Vec<_>>();
let mut inputs = [[BPField::from(0); 9]; 64];
for (i, inner) in inputs.iter_mut().enumerate() {
for (j, x) in inner.iter_mut().enumerate() {
*x = BPField::from((i + j) as u32);
}
}
let proof = runtime.prove(program, inputs, vec![], vec![]).unwrap();
let proof = runtime
.prove(program, vec![inputs], vec![], vec![])
.unwrap();
runtime
.verify(program, &proof, Vec::<ZkpProgramInput>::new(), vec![])

View File

@@ -48,7 +48,12 @@ pub fn lift_type(arg_type: &Type) -> Result<Type, ProgramTypeError> {
/**
* Emits code to make a program node for the given type T.
*/
pub fn create_program_node(var_name: &str, arg_type: &Type, input_method: &str) -> TokenStream2 {
pub fn create_program_node(
var_name: &str,
arg_type: &Type,
input_method: &str,
input_arg: Option<&Ident>,
) -> TokenStream2 {
let mapped_type = match lift_type(arg_type) {
Ok(v) => v,
Err(ProgramTypeError::IllegalType(s)) => {
@@ -71,7 +76,7 @@ pub fn create_program_node(var_name: &str, arg_type: &Type, input_method: &str)
};
quote_spanned! {arg_type.span() =>
let #var_name: #mapped_type = #type_annotation::#input_method();
let #var_name: #mapped_type = #type_annotation::#input_method(#input_arg);
}
}
@@ -433,7 +438,7 @@ mod test {
let type_name: Type = parse_quote!(#type_name);
let actual = create_program_node("horse", &type_name, "input");
let actual = create_program_node("horse", &type_name, "input", None);
let expected = quote! {
let horse: ProgramNode<Cipher<Rational> > = ProgramNode::input();
@@ -450,7 +455,7 @@ mod test {
let type_name: Type = parse_quote!(#type_name);
let actual = create_program_node("horse", &type_name, "doggie");
let actual = create_program_node("horse", &type_name, "doggie", None);
let expected = quote! {
let horse: ProgramNode<Cipher<Rational> > = ProgramNode::doggie();
@@ -459,6 +464,24 @@ mod test {
assert_syn_eq(&actual, &expected);
}
#[test]
fn can_pass_args_to_input() {
let type_name = quote! {
BfvPlaintext<Signed>
};
let arg = format_ident!("input_arg");
let type_name: Type = parse_quote!(#type_name);
let actual = create_program_node("pt", &type_name, "linked_input", Some(&arg));
let expected = quote! {
let pt: ProgramNode<BfvPlaintext<Signed> > = ProgramNode::linked_input(input_arg);
};
assert_syn_eq(&actual, &expected);
}
#[test]
fn can_create_array_program_node() {
let type_name = quote! {
@@ -467,7 +490,7 @@ mod test {
let type_name: Type = parse_quote!(#type_name);
let actual = create_program_node("horse", &type_name, "input");
let actual = create_program_node("horse", &type_name, "input", None);
let expected = quote! {
let horse: [ProgramNode<Cipher<Rational> >; 7] = <[ProgramNode<Cipher<Rational> >; 7]>::input();
@@ -484,7 +507,7 @@ mod test {
let type_name: Type = parse_quote!(#type_name);
let actual = create_program_node("horse", &type_name, "input");
let actual = create_program_node("horse", &type_name, "input", None);
let expected = quote! {
let horse: [[ProgramNode<Cipher<Rational> >; 7]; 6] = <[[ProgramNode<Cipher<Rational> >; 7]; 6]>::input();

View File

@@ -61,7 +61,7 @@ fn get_binary_operands<O: Operation>(
* # Remarks
* CSE is an optimization that collapses and reuses redundance
* computations. For example:
* ```ignore
* ```text
* a = b + c * d
* e = c * d + 42
* ```

View File

@@ -1,5 +1,5 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use sunscreen_compiler_common::macros::{
create_program_node, emit_signature, extract_fn_arguments, ExtractFnArgumentsError,
};
@@ -13,8 +13,9 @@ use crate::{
};
enum ArgumentKind {
Public,
Linked,
Private,
Public,
Constant,
}
@@ -106,8 +107,10 @@ fn parse_inner(_attr_params: ZkpProgramAttrs, input_fn: ItemFn) -> Result<TokenS
}
};
let mut is_linked = false;
let mut public_seen = false;
let mut constant_seen = false;
let mut private_seen = false;
let unwrapped_inputs = match extract_fn_arguments(inputs) {
Ok(args) => {
args.iter().map(|a| {
@@ -120,17 +123,28 @@ fn parse_inner(_attr_params: ZkpProgramAttrs, input_fn: ItemFn) -> Result<TokenS
"private arguments must be specified before #[public] and #[constant] arguments"
));
}
private_seen = true;
},
[attr] => {
let ident = attr.path().get_ident();
match ident.map(|x| x.to_string()).as_deref() {
Some("linked") => {
if private_seen || public_seen || constant_seen {
return Err(Error::compile_error(attr.path().span(),
"#[linked] arguments must be specified before #[private], #[public] and #[constant] arguments"
));
}
arg_kind = ArgumentKind::Linked;
is_linked = true;
},
Some("private") => {
if public_seen || constant_seen {
return Err(Error::compile_error(attr.path().span(),
"#[private] arguments must be specified before #[public] and #[constant] arguments"
));
}
private_seen = true;
},
Some("public") => {
if constant_seen {
@@ -147,14 +161,14 @@ fn parse_inner(_attr_params: ZkpProgramAttrs, input_fn: ItemFn) -> Result<TokenS
},
_ => {
return Err(Error::compile_error(attr.path().span(), &format!(
"Expected #[private], #[public] or #[constant], found {}",
"Expected #[linked], #[private], #[public] or #[constant], found {}",
attr.path().to_token_stream()
)));
}
}
},
[_, attr, ..] => {
return Err(Error::compile_error(attr.span(), "ZKP program arguments may only have one attribute (#[private], #[public] or #[constant])."));
return Err(Error::compile_error(attr.span(), "ZKP program arguments may only have one attribute (#[linked], #[private], #[public] or #[constant])."));
}
};
@@ -174,14 +188,17 @@ fn parse_inner(_attr_params: ZkpProgramAttrs, input_fn: ItemFn) -> Result<TokenS
let signature = emit_signature(&argument_types, &[]);
let build_arg = format_ident!("linked_input");
let var_decl = unwrapped_inputs.iter().map(|t| {
let input_type = match t.0 {
ArgumentKind::Private => "private_input",
ArgumentKind::Public => "public_input",
ArgumentKind::Constant => "constant_input",
let (input_type, input_arg) = match t.0 {
ArgumentKind::Linked => ("linked_input", Some(&build_arg)),
ArgumentKind::Private => ("private_input", None),
ArgumentKind::Public => ("public_input", None),
ArgumentKind::Constant => ("constant_input", None),
};
create_program_node(&t.2.to_string(), t.1, input_type)
create_program_node(&t.2.to_string(), t.1, input_type, input_arg)
});
let zkp_program_struct_name =
@@ -189,6 +206,18 @@ fn parse_inner(_attr_params: ZkpProgramAttrs, input_fn: ItemFn) -> Result<TokenS
let zkp_program_name_literal = format!("{}", zkp_program_name);
let (associated_link_type, into_linked_variant, ext_impl) = if is_linked {
(quote! { sunscreen::zkp::Linked }, quote! {Ok}, None)
} else {
(
quote! { sunscreen::zkp::NotLinked },
quote! {Err},
Some(quote! {
impl sunscreen::ZkpProgramFnExt for #zkp_program_struct_name {}
}),
)
};
Ok(quote! {
#[allow(non_camel_case_types)]
#[derive(Clone)]
@@ -201,10 +230,12 @@ fn parse_inner(_attr_params: ZkpProgramAttrs, input_fn: ItemFn) -> Result<TokenS
}
impl <#generic_ident: #generic_bound> sunscreen::ZkpProgramFn<#generic_ident> for #zkp_program_struct_name {
fn build(&self) -> sunscreen::Result<sunscreen::zkp::ZkpFrontendCompilation> {
type Link = #associated_link_type;
fn build(&self, linked_input: <Self::Link as sunscreen::zkp::Link>::Input) -> sunscreen::Result<sunscreen::zkp::ZkpFrontendCompilation> {
use std::cell::RefCell;
use std::mem::transmute;
use sunscreen::{Error, INDEX_ARENA, Result, types::{zkp::{ProgramNode, CreateZkpProgramInput, ConstrainEq, IntoProgramNode}, TypeName}, zkp::{CURRENT_ZKP_CTX, ZkpContext, ZkpData}};
use sunscreen::{Error, INDEX_ARENA, Result, types::{zkp::{ProgramNode, CreateLinkedZkpProgramInput, CreateZkpProgramInput, ConstrainEq, IntoProgramNode}, TypeName}, zkp::{CURRENT_ZKP_CTX, ZkpContext, ZkpData}};
let mut context = ZkpContext::new(ZkpData::new());
@@ -250,9 +281,14 @@ fn parse_inner(_attr_params: ZkpProgramAttrs, input_fn: ItemFn) -> Result<TokenS
fn signature(&self) -> sunscreen::CallSignature {
#signature
}
#[allow(clippy::type_complexity)]
fn into_linked(self) -> Result<std::boxed::Box<dyn sunscreen::ZkpProgramFn<F, Link = sunscreen::zkp::Linked>>, std::boxed::Box<dyn sunscreen::ZkpProgramFn<F, Link = sunscreen::zkp::NotLinked>>> {
#into_linked_variant(std::boxed::Box::new(self))
}
}
impl sunscreen::ZkpProgramFnExt for #zkp_program_struct_name {}
#ext_impl
#[allow(non_upper_case_globals)]
#vis const #zkp_program_name: #zkp_program_struct_name = #zkp_program_struct_name;

View File

@@ -5,7 +5,7 @@ Using the `Batched` data type allow up to 4-5 orders of magnitude more throughpu
Operations on `Batched` work element-wise on values contained within the two operands. If you've ever written SSE or AVX code before, this is very similar idea. Here's a simplified example (with only 1 row of values) of what this looks like.
```ignore
```text
a = [0, 1, 2, 3];
b = [4, 5, 6, 7];
@@ -34,7 +34,7 @@ Additionally, the `Batched` type features a number of rotations
Under rotations, lanes wrap around the left or right side. In this example, we rotate a batched type left by 3 places
```ignore
```text
[[0, 1, 2, 3], [4, 5, 6, 7]] << 3
= [[3, 0, 1, 2], [7, 4, 5, 6]]
```
@@ -46,4 +46,4 @@ The maximum value `LANES` can be depends on the scheme parameters. To get the ma
When compiling FHE programs using the `Batched` type, you must set a plain modulus constraint of `PlainModulusConstraint::BatchingMinimum(x)`. This will select a plain modulus on your behalf that suitable for batching with at least `x` bits of precision. When using this constraint, Sunscreen chooses the exact value of `p`, which you can inspect after compilation by looking at `my_program.metadata.params.plain_modulus`. Each lane supports values in the range `[-p / 2, p / 2]`[^1] where `p` is the resolved plain modulus.
[^1] Negative values are stored using [`p`'s-complement](https://en.wikipedia.org/wiki/Method_of_complements). When batching, `p` is prime and thus odd, resulting in a balanced range.
[^1] Negative values are stored using [`p`'s-complement](https://en.wikipedia.org/wiki/Method_of_complements). When batching, `p` is prime and thus odd, resulting in a balanced range.

View File

@@ -54,7 +54,7 @@ A high level approach to efficiently computing a dot product with FHE is:
4. Sum the two rows together by adding the sum variable to a `swap_rows()` version of itself.
To show why steps 3 and 4 sum the lanes of a `Batched` value, let's walk through an example
```ignore
```text
c =
[[01, 02, 03, 04, 05, 06, 07, 08], [09, 10, 11, 12, 13, 14, 15, 16]]
@@ -77,7 +77,7 @@ c = c + (c << 4)
After running step 3, we have 2 rows where every column contains the sum of all the columns in the respective row for the original c vector.
Next, we simply need to swap the rows and add
```ignore
```text
c = c + c.swapRows()
[[036, 036, 036, 036, 036, 036, 036, 036], [100, 100, 100, 100, 100, 100, 100, 100]]
+ [[100, 100, 100, 100, 100, 100, 100, 100], [036, 036, 036, 036, 036, 036, 036, 036]]

View File

@@ -6,7 +6,7 @@ Why would we want to do this? FHE operations actually add and multiply [polynomi
## Addition
Let's start with a simple carryless addition example. Adding 99 + 43 without propagating carries gives us:
```ignore
```text
9 9
+ 4 3
------
@@ -19,7 +19,7 @@ Furthermore, we can represent negative values by negating each digit. For exampl
We can easily extend this reasoning to base 2 (binary). Under carryless arithmetic, base 2 simply means that each digit is a multiple of a power of 2. For example, we can compute `3 + 2 + (-4)` as follows:
```ignore
```text
3 + 2 =
1 1 = 3
+ 1 0 = 2
@@ -59,7 +59,7 @@ Note that the coefficients are the same as the digits in our carryless arithmeti
## Multiplication
Now, lets go through a multiplication example with binary carryless arithmetic. Here, we multiply `7 * 13 = 91`:
```ignore
```text
0 1 1 1 = 7
* 1 1 0 1 = 13
---------------
@@ -109,7 +109,7 @@ Suppose `p = 7` and we try to add `3 + 1`. Values should be within the range \\(
### Addition
Let's look at an addition example, treating the coefficients as carryless arithmetic binary digits. Take `p = 9` (meaning digits are in the interval \\([-4,4]\\)) and add `15 = 4 0 -1`[^3] with `8 = 2 2 -4`.
```ignore
```text
4 0 -1 = 15
+ 2 2 -4 = 8
--------
@@ -127,7 +127,7 @@ If we increase `p` to `13` and repeat the example, we do get the correct answer
### Multiplication
Next, let's consider canonical representations of `31 = 1 1 1 1 1` and `15 = 0 1 1 1 1` when `p = 7` (i.e. digits in interval \\([-3, 3]\\)). Adding these numbers doesn't lead to overflow, but what about multiplication? Let's find out:
```ignore
```text
1 1 1 1 1 = 31
* 0 1 1 1 1 = 15
--------------------------

View File

@@ -57,7 +57,7 @@ cargo build --bin myapp --release --target wasm32-unknown-emscripten
where `myapp` is the name of your executable.
On success, you should see the following files:
```ignore
```text
target/wasm32-unknown-emscripten/release/myapp.js
target/wasm32-unknown-emscripten/release/myapp.wasm
```

View File

@@ -24,7 +24,7 @@ It's a type wrapper needed to compile your FHE program. Internally, the `#[fhe_p
Usually, these errors tell you an `FheProgramNode`'s inner type doesn't
support an operation you're trying to perform. In the example below, the compiler is saying you can't divide `Signed` values:
```ignore
```text
error[E0369]: cannot divide `FheProgramNode<Cipher<Signed>>` by `FheProgramNode<Cipher<Signed>>`
--> examples/simple_multiply/src/main.rs:22:7
|

View File

@@ -57,7 +57,7 @@ performance is to enable tracing[^trace].
If you run this ZKP program on the polynomial with coefficients `1,...,100`
evaluated at the point `2`, you'll see a trace like the following:
```ignore
```text
[TRACE sunscreen_runtime::runtime] Starting JIT (prover)...
[TRACE sunscreen_runtime::runtime] Prover JIT time 0.002542035s
[TRACE sunscreen_runtime::runtime] Starting backend prove...
@@ -98,7 +98,7 @@ the factor \\(n\\).
If we change the argument types from `#[public]` to `#[constant]` and rerun the
trace, we'll see a big improvement:
```ignore
```text
[TRACE sunscreen_runtime::runtime] Starting JIT (prover)...
[TRACE sunscreen_runtime::runtime] Prover JIT time 0.002529488s
[TRACE sunscreen_runtime::runtime] Starting backend prove...

View File

@@ -62,7 +62,7 @@ where `myapp` is the name of your executable.
On success, you should see the following files:
```ignore
```text
target/wasm32-unknown-emscripten/release/myapp.js
target/wasm32-unknown-emscripten/release/myapp.wasm
```

View File

@@ -13,7 +13,7 @@ that code between both the prover and verifier. This is easily accomplished with
a [Cargo workspace](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html).
For example, the workspace might have a file structure like this:
```ignore
```text
.
├── Cargo.lock
├── Cargo.toml

View File

@@ -17,7 +17,7 @@ Usually, these errors tell you a `ProgramNode` doesn't support an operation
you're trying to perform. In the example below, the compiler is saying you can't
compare values:
```ignore
```text
error[E0369]: binary operation `>` cannot be applied to type `ProgramNode<Field<F>>`
--> examples/ordering_zkp/src/main.rs:13:15
|

View File

@@ -19,17 +19,19 @@ readme = "crates-io.md"
[dependencies]
bincode = { workspace = true }
bitvec = { workspace = true, optional = true }
bulletproofs = { workspace = true , optional = true}
bulletproofs = { workspace = true, optional = true }
crossbeam = { workspace = true }
curve25519-dalek = { workspace = true }
log = { workspace = true }
logproof = { workspace = true , optional = true}
logproof = { workspace = true, optional = true }
merlin = { workspace = true }
seal_fhe = { workspace = true }
seq-macro = { version = "0.3", optional = true }
sunscreen_fhe_program = { workspace = true }
sunscreen_compiler_common = { workspace = true }
sunscreen_math = { workspace = true }
sunscreen_zkp_backend = { workspace = true }
paste = { workspace = true, optional = true }
petgraph = { workspace = true }
rayon = { workspace = true }
rlp = { workspace = true }
@@ -42,6 +44,11 @@ thiserror = { workspace = true }
serde_json = { workspace = true }
[features]
bulletproofs = []
linkedproofs = ["dep:bitvec", "dep:bulletproofs", "dep:logproof"]
linkedproofs = [
"bulletproofs",
"dep:logproof",
"dep:bitvec",
"dep:seq-macro",
"dep:paste",
]
deterministic = ["seal_fhe/deterministic"]

View File

@@ -0,0 +1,633 @@
//! This module contains various builders for ZKPs, SDLPs, and linked proofs.
use sunscreen_zkp_backend::{Proof, ZkpBackend};
use crate::{marker, CompiledZkpProgram, GenericRuntime, Params, Result, ZkpProgramInput};
/// Errors that can occur when building a log proof or linked proof.
#[derive(PartialEq, Eq, Debug, Clone, thiserror::Error)]
pub enum BuilderError {
/// An error with the ZKP proving.
#[error("These FHE parameters are not supported by logproof: {0:?}")]
UnsupportedParameters(Box<Params>),
/// An error generating the runtime.
#[error("Invalid usage: {0}")]
InvalidUsage(Box<String>),
}
impl BuilderError {
fn user_error(msg: impl Into<String>) -> crate::Error {
Self::InvalidUsage(Box::new(msg.into())).into()
}
}
/// A builder for creating a ZKP.
///
/// This is offered as a convenience for building the arguments necessary for the
/// [`prove`][GenericRuntime::prove] function.
pub struct ProofBuilder<'r, 'p, T: marker::Zkp, B: ZkpBackend> {
runtime: &'r GenericRuntime<T, B>,
program: &'p CompiledZkpProgram,
private_inputs: Vec<ZkpProgramInput>,
public_inputs: Vec<ZkpProgramInput>,
constant_inputs: Vec<ZkpProgramInput>,
}
impl<'r, 'p, T: marker::Zkp, B: ZkpBackend> ProofBuilder<'r, 'p, T, B> {
/// Create a new `ProofBuilder`. It's typically more convenient to create a proof builder
/// via [`runtime.proof_builder()`][GenericRuntime::proof_builder].
pub fn new(runtime: &'r GenericRuntime<T, B>, program: &'p CompiledZkpProgram) -> Self
where
T: marker::Zkp,
B: ZkpBackend,
{
Self {
runtime,
program,
private_inputs: vec![],
public_inputs: vec![],
constant_inputs: vec![],
}
}
/// Add a constant input to the proof builder.
pub fn constant_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.constant_inputs.push(input.into());
self
}
/// Add multiple constant inputs to the proof builder.
pub fn constant_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.constant_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Add a public input to the proof builder.
pub fn public_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.public_inputs.push(input.into());
self
}
/// Add multiple public inputs to the proof builder.
pub fn public_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.public_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Add a private input to the proof builder.
pub fn private_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.private_inputs.push(input.into());
self
}
/// Add multiple private inputs to the proof builder.
pub fn private_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.private_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Generate a proof; see [`runtime.prove()`][GenericRuntime::prove].
pub fn prove(self) -> Result<Proof> {
self.runtime.prove(
self.program,
self.private_inputs,
self.public_inputs,
self.constant_inputs,
)
}
}
/// A builder for verifying a proof.
///
/// This is offered as a convenience for building the arguments necessary for the
/// [`verify`][GenericRuntime::verify] function.
pub struct VerificationBuilder<'r, 'p, 'a, T: marker::Zkp, B: ZkpBackend> {
runtime: &'r GenericRuntime<T, B>,
program: &'p CompiledZkpProgram,
proof: Option<&'a Proof>,
constant_inputs: Vec<ZkpProgramInput>,
public_inputs: Vec<ZkpProgramInput>,
}
impl<'r, 'p, 'a, T: marker::Zkp, B: ZkpBackend> VerificationBuilder<'r, 'p, 'a, T, B> {
/// Create a new `VerificationBuilder`. It's typically more convenient to create a
/// verification builder via
/// [`runtime.verification_builder()`][GenericRuntime::verification_builder].
pub fn new(runtime: &'r GenericRuntime<T, B>, program: &'p CompiledZkpProgram) -> Self
where
T: marker::Zkp,
B: ZkpBackend,
{
Self {
runtime,
program,
proof: None,
public_inputs: vec![],
constant_inputs: vec![],
}
}
/// Add the proof to verify.
pub fn proof(mut self, proof: &'a Proof) -> Self {
self.proof = Some(proof);
self
}
/// Add a constant input to the verification builder.
pub fn constant_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.constant_inputs.push(input.into());
self
}
/// Add multiple constant inputs to the verification builder.
pub fn constant_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.constant_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Add a public input to the verification builder.
pub fn public_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.public_inputs.push(input.into());
self
}
/// Add multiple public inputs to the verification builder.
pub fn public_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.public_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Verify that `self.proof` satisfies `self.program`; see
/// [`runtime.verify()`][GenericRuntime::verify].
///
/// # Remarks
/// Will error if the underlying `verify` call errors, or if a proof has not yet been
/// supplied to the builder. That is, you must call [`Self::proof`] before calling this
/// function.
pub fn verify(self) -> Result<()> {
let proof = self.proof.ok_or_else(|| {
BuilderError::user_error(
"You must supply a proof to the verification builder before calling `verify`",
)
})?;
self.runtime.verify(
self.program,
proof,
self.public_inputs,
self.constant_inputs,
)
}
}
#[cfg(feature = "linkedproofs")]
pub use linked::*;
#[cfg(feature = "linkedproofs")]
mod linked {
use super::BuilderError;
use std::{borrow::Cow, sync::Arc};
use logproof::{
bfv_statement::{self, BfvMessage, BfvProofStatement, BfvWitness, StatementParams},
rings::{SealQ128_1024, SealQ128_2048, SealQ128_4096, SealQ128_8192},
Bounds, LogProofProverKnowledge,
};
use seal_fhe as seal;
use seal_fhe::SecurityLevel;
use sunscreen_compiler_common::{Type, TypeName};
use sunscreen_math::ring::{BarrettBackend, BarrettConfig, Zq};
use sunscreen_zkp_backend::{
bulletproofs::{BulletproofsBackend, BulletproofsFieldSpec},
FieldSpec,
};
use crate::{
marker, BFVEncryptionComponents, Ciphertext, CompiledZkpProgram, GenericRuntime,
LinkedProof, NumCiphertexts, Params, Plaintext, PublicKey, Result, Sdlp,
SealSdlpProverKnowledge, TryIntoPlaintext, ZkpProgramInput,
};
/// All FHE plaintext types can be used in a [`Sdlp`]. This trait indicates further that a
/// plaintext type can be linked between the SDLP and a ZKP program.
pub trait LinkWithZkp: NumCiphertexts {
/// The associated ZKP input type when this plaintext is linked with a ZKP program.
type ZkpType<F: FieldSpec>: TypeName;
/// The number of nonzero coefficients to link between the SDLP and ZKP.
///
/// Note that many FHE plaintext types are encoded into polynomials with a flavor of binary
/// or 2s complement encoding; that is, as coefficients of powers of 2. In this case, the
/// `DEGREE_BOUND` must be chosen carefully to ensure that the field elements don't
/// overflow.
///
/// For example, if you include 256 coefficients of a plaintext polynomial encoding a
/// `Signed` value, the ZKP circuit will attempt to multiply the last coefficient by
/// `2^{256}`. If using the bulletproofs backend, the `Scalar` type backing the field
/// elements is only a 256-bit integer, and will overflow! So, set this value carefully
/// depending on the plaintext encoding and field element decoding logic.
const DEGREE_BOUND: usize;
}
#[derive(Debug, Clone)]
/// We pass this around for both plain and linked messages, as we need both the plaintext and
/// its type information to perform encryption.
///
/// Essentially we store the output from TryIntoPlaintext and TypeName.
pub(crate) struct PlaintextTyped {
plaintext: Plaintext,
type_name: Type,
}
/// We pass this around for just linked messages, as also need the zkp type information.
///
/// Essentially we store the output from LinkWithZkp
#[derive(Debug, Clone)]
pub(crate) struct LinkedPlaintextTyped {
plaintext_typed: PlaintextTyped,
zkp_type: Type,
}
/// A [`Plaintext`] message that can be linked. Create this with [`LogProofBuilder::encrypt_and_link`].
#[derive(Debug, Clone)]
pub struct LinkedMessage {
pub(crate) id: usize,
pub(crate) message: Arc<LinkedPlaintextTyped>,
pub(crate) len: usize,
}
enum Message {
Plain(PlaintextTyped),
Linked(LinkedMessage),
}
impl Message {
fn pt_typed(&self) -> &PlaintextTyped {
match self {
Message::Plain(m) => m,
Message::Linked(m) => &m.message.plaintext_typed,
}
}
fn pt(&self) -> &Plaintext {
&self.pt_typed().plaintext
}
fn type_name(&self) -> &Type {
&self.pt_typed().type_name
}
fn linked_id(&self) -> Option<usize> {
match self {
Message::Linked(LinkedMessage { id, .. }) => Some(*id),
_ => None,
}
}
}
/// A builder for [`Sdlp`] or [`LinkedProof`].
///
/// Use this builder to encrypt your [`Plaintext`]s while automatically generate a log proof of the
/// encryption statements. We implicitly assume that these plaintexts and ciphertexts are backed by
/// the SEAL BFV scheme, otherwise the methods will return an `Err`.
pub struct LogProofBuilder<'r, 'k, 'z, M, B> {
runtime: &'r GenericRuntime<M, B>,
// log proof fields
statements: Vec<BfvProofStatement<'k>>,
messages: Vec<BfvMessage>,
witness: Vec<BfvWitness<'k>>,
// linked proof fields
compiled_zkp_program: Option<&'z CompiledZkpProgram>,
linked_inputs: Vec<LinkedMessage>,
private_inputs: Vec<ZkpProgramInput>,
public_inputs: Vec<ZkpProgramInput>,
constant_inputs: Vec<ZkpProgramInput>,
}
impl<'r, 'k, 'z, M: marker::Fhe, Z> LogProofBuilder<'r, 'k, 'z, M, Z> {
/// Create a new [`LogProofBuilder`].
pub fn new(runtime: &'r GenericRuntime<M, Z>) -> Self {
Self {
runtime,
statements: vec![],
messages: vec![],
witness: vec![],
compiled_zkp_program: None,
linked_inputs: vec![],
private_inputs: vec![],
public_inputs: vec![],
constant_inputs: vec![],
}
}
/// Encrypt a plaintext, adding the encryption statement to the proof.
///
/// If you do not want to add the encryption statement to the proof, just use [the
/// runtime](`crate::GenericRuntime::encrypt`) directly.
pub fn encrypt<P>(&mut self, message: &P, public_key: &'k PublicKey) -> Result<Ciphertext>
where
P: TryIntoPlaintext + TypeName,
{
let pt = self.plaintext_typed(message)?;
self.encrypt_internal(Message::Plain(pt), public_key, None)
}
/// Encrypt a plaintext intended for sharing.
///
/// The returned `LinkedMessage` can be used:
/// 1. to add an encryption statement of ciphertext equality to the proof (see [`Self::encrypt_linked`]).
/// 2. as a linked input to a ZKP program (see [`Self::linked_input`]).
pub fn encrypt_and_link<P>(
&mut self,
message: &P,
public_key: &'k PublicKey,
) -> Result<(Ciphertext, LinkedMessage)>
where
P: LinkWithZkp + TryIntoPlaintext + TypeName,
{
// The user intends to link this message, so add a more conservative bound
let plaintext_typed = self.plaintext_typed(message)?;
let idx_start = self.messages.len();
let ct = self.encrypt_internal(
Message::Plain(plaintext_typed.clone()),
public_key,
Some(self.mk_bounds::<P>()),
)?;
let idx_end = self.messages.len();
// TODO shouldn't be assuming bulletproofs here...
// need to separate the notion of sharing duplicate messages and sharing to ZKP
let zkp_type = P::ZkpType::<BulletproofsFieldSpec>::type_name();
let linked_message = LinkedMessage {
id: idx_start,
message: Arc::new(LinkedPlaintextTyped {
plaintext_typed,
zkp_type,
}),
len: idx_end - idx_start,
};
Ok((ct, linked_message))
}
/// Encrypt a linked message, adding the new encryption statement to the proof.
///
/// This method purposefully reveals that two ciphertexts enrypt the same underlying value. If
/// this is not what you want, use [`Self::encrypt`].
///
/// This method assumes that you've created the `message` argument with _this_ builder.
pub fn encrypt_linked(
&mut self,
message: &LinkedMessage,
public_key: &'k PublicKey,
) -> Result<Ciphertext> {
// The existing message already has bounds, no need to recompute them.
let bounds = None;
self.encrypt_internal(Message::Linked(message.clone()), public_key, bounds)
}
fn encrypt_internal(
&mut self,
message: Message,
public_key: &'k PublicKey,
bounds: Option<Bounds>,
) -> Result<Ciphertext> {
let enc_components = self.runtime.encrypt_return_components_switched_internal(
message.pt(),
message.type_name(),
public_key,
true,
None,
)?;
let existing_idx = message.linked_id();
for (i, AsymmetricEncryption { ct, u, e, r, m }) in
zip_seal_pieces(&enc_components, message.pt())?.enumerate()
{
let message_id = if let Some(idx) = existing_idx {
idx + i
} else {
let idx = self.messages.len();
self.messages.push(BfvMessage {
plaintext: m.clone(),
bounds: bounds.clone(),
});
idx
};
self.statements
.push(BfvProofStatement::PublicKeyEncryption {
message_id,
ciphertext: ct.clone(),
public_key: Cow::Borrowed(&public_key.public_key.data),
});
self.witness
.push(BfvWitness::PublicKeyEncryption { u, e, r: r.clone() });
}
Ok(enc_components.ciphertext)
}
fn plaintext_typed<P>(&self, pt: &P) -> Result<PlaintextTyped>
where
P: TryIntoPlaintext + TypeName,
{
Ok(PlaintextTyped {
plaintext: pt.try_into_plaintext(self.runtime.params())?,
type_name: P::type_name(),
})
}
/// Build the [`Sdlp`] for the statements added to this builder.
///
/// You can use this as a standalone proof, or alternatively see
/// [`Self::build_linkedproof`] to prove additional properties about the underlying
/// plaintexts.
pub fn build_logproof(&self) -> Result<Sdlp> {
Sdlp::create(&self.build_sdlp_pk()?)
}
fn build_sdlp_pk(&self) -> Result<SealSdlpProverKnowledge> {
let params = self.runtime.params();
match (params.lattice_dimension, params.security_level) {
(1024, SecurityLevel::TC128) => Ok(SealSdlpProverKnowledge::from(
self.build_sdlp_pk_generic::<1, SealQ128_1024>()?,
)),
(2048, SecurityLevel::TC128) => Ok(SealSdlpProverKnowledge::from(
self.build_sdlp_pk_generic::<1, SealQ128_2048>()?,
)),
(4096, SecurityLevel::TC128) => Ok(SealSdlpProverKnowledge::from(
self.build_sdlp_pk_generic::<2, SealQ128_4096>()?,
)),
(8192, SecurityLevel::TC128) => Ok(SealSdlpProverKnowledge::from(
self.build_sdlp_pk_generic::<3, SealQ128_8192>()?,
)),
_ => Err(BuilderError::UnsupportedParameters(Box::new(params.clone())).into()),
}
}
fn build_sdlp_pk_generic<const N: usize, B: BarrettConfig<N>>(
&self,
) -> Result<LogProofProverKnowledge<Zq<N, BarrettBackend<N, B>>>> {
let params = self.runtime.params();
let ctx = self.runtime.context();
Ok(bfv_statement::generate_prover_knowledge(
&self.statements,
&self.messages,
&self.witness,
params,
ctx,
))
}
fn mk_bounds<P: LinkWithZkp>(&self) -> Bounds {
let params = self.runtime.params();
let mut bounds = vec![params.plain_modulus; P::DEGREE_BOUND];
bounds.resize(params.lattice_dimension as usize, 0);
Bounds(bounds)
}
}
impl<'r, 'k, 'z, M: marker::Fhe + marker::Zkp> LogProofBuilder<'r, 'k, 'z, M, BulletproofsBackend> {
/// Add a ZKP program to be linked with the logproof.
///
/// This method is required to call [`Self::build_linkedproof`].
pub fn zkp_program(&mut self, program: &'z CompiledZkpProgram) -> Result<&mut Self> {
let params = program.metadata.params.as_ref().ok_or_else(|| {
BuilderError::user_error(
"Cannot link a ZKP program without associated FHE parameters. Make sure your ZKP program has #[linked] parameters and is compiled alongside an FHE program.",
)
})?;
if params != self.runtime.params() {
return Err(BuilderError::user_error(
"The FHE parameters of the ZKP program do not match the FHE parameters of the runtime.",
));
}
self.compiled_zkp_program = Some(program);
Ok(self)
}
/// Add a linked private input to the ZKP program.
///
/// This method assumes that you've created the `message` argument with _this_ builder.
pub fn linked_input(&mut self, message: &LinkedMessage) -> &mut Self {
self.linked_inputs.push(message.clone());
self
}
/// Add a private input to the ZKP program.
pub fn private_input(&mut self, input: impl Into<ZkpProgramInput>) -> &mut Self {
self.private_inputs.push(input.into());
self
}
/// Add a public input to the ZKP program.
pub fn public_input(&mut self, input: impl Into<ZkpProgramInput>) -> &mut Self {
self.public_inputs.push(input.into());
self
}
/// Add a constant input to the proof builder.
pub fn constant_input(&mut self, input: impl Into<ZkpProgramInput>) -> &mut Self {
self.constant_inputs.push(input.into());
self
}
/// Output a [`LinkedProof`] from the encryption statements and ZKP program and inputs added to
/// this builder.
pub fn build_linkedproof(&mut self) -> Result<crate::linked::LinkedProof> {
let sdlp = self.build_sdlp_pk()?;
let program = self.compiled_zkp_program.ok_or_else(|| {
BuilderError::user_error("Cannot build linked proof without a compiled ZKP program. Use the `.zkp_program()` method")
})?;
let linked_indices = self
.linked_inputs
.iter()
.flat_map(|m| (m.id..m.id + m.len).map(|ix| (ix, 0)))
.collect::<Vec<_>>();
let linked_types = self
.linked_inputs
.iter()
.map(|m| m.message.zkp_type.clone())
.collect::<Vec<_>>();
LinkedProof::create(
&sdlp,
&linked_indices,
&linked_types,
program,
self.private_inputs.clone(),
self.public_inputs.clone(),
self.constant_inputs.clone(),
)
}
}
impl StatementParams for Params {
fn degree(&self) -> u64 {
self.lattice_dimension
}
fn plain_modulus(&self) -> u64 {
self.plain_modulus
}
fn ciphertext_modulus(&self) -> Vec<u64> {
self.coeff_modulus.clone()
}
}
// Helper struct when decomposing the encryption pieces.
struct AsymmetricEncryption<'a> {
ct: &'a seal::Ciphertext,
u: seal::PolynomialArray,
e: seal::PolynomialArray,
r: &'a seal::Plaintext,
m: &'a seal::Plaintext,
}
fn zip_seal_pieces<'a>(
enc_components: &'a BFVEncryptionComponents,
pt: &'a Plaintext,
) -> Result<impl Iterator<Item = AsymmetricEncryption<'a>>> {
let seal_cts = enc_components
.ciphertext
.inner_as_seal_ciphertext()?
.iter()
.map(|ct| &ct.data);
let seal_pts = pt.inner_as_seal_plaintext()?.iter();
let us = enc_components.u.clone().into_iter();
let es = enc_components.e.clone().into_iter();
let rs = enc_components
.r
.iter()
.map(|r| &r.inner_as_seal_plaintext().unwrap()[0].data);
Ok(seal_cts
.zip(seal_pts)
.zip(us)
.zip(es)
.zip(rs)
.map(|((((ct, pt), u), e), r)| AsymmetricEncryption { ct, u, e, r, m: pt }))
}
}

View File

@@ -123,10 +123,20 @@ pub enum Error {
ZkpError(#[from] ZkpError),
/**
* An error occurred when building a proof or verification
* An error occurred from incorrect usage of a builder.
*/
#[error("ZKP builder error: {0}")]
ZkpBuilderError(Box<String>),
#[error("Builder error: {0}")]
BuilderError(#[from] crate::builder::BuilderError),
/// Error when proving or verifying a linked proof.
#[cfg(feature = "linkedproofs")]
#[error("Linked proof error: {0}")]
LinkedProofError(#[from] crate::linked::LinkedProofError),
/// Error when proving or verifying a solo logproof.
#[cfg(feature = "linkedproofs")]
#[error("Log proof error: {0}")]
LogProofError(#[from] logproof::ProofError),
}
const_assert!(std::mem::size_of::<Error>() <= 24);
@@ -153,13 +163,6 @@ impl Error {
Self::FheTypeError(Box::new(msg.to_owned()))
}
/**
* Create an [`Error::ZkpBuilderError`].
*/
pub fn zkp_builder_error(msg: &str) -> Self {
Self::ZkpBuilderError(Box::new(msg.to_owned()))
}
fn unwrap_argument_mismatch_data(&self) -> &(Vec<Type>, Vec<Type>) {
match self {
Self::ArgumentMismatch(d) => d,
@@ -184,4 +187,4 @@ impl From<bincode::Error> for Error {
/**
* Wrapper around [`Result`](std::result::Result) with this crate's error type.
*/
pub type Result<T> = std::result::Result<T, Error>;
pub type Result<T, E = Error> = std::result::Result<T, E>;

View File

@@ -4,32 +4,32 @@
//! This crate contains the types and functions for executing a Sunscreen FHE or ZKP program.
mod array;
mod builder;
mod error;
mod keys;
#[cfg(feature = "linkedproofs")]
mod linked;
mod metadata;
mod run;
mod runtime;
mod serialization;
#[cfg(feature = "linkedproofs")]
mod linked;
#[cfg(feature = "linkedproofs")]
pub use crate::linked::*;
use std::sync::Arc;
pub use crate::error::*;
pub use crate::keys::*;
pub use crate::metadata::*;
pub use run::*;
pub use runtime::*;
pub use serialization::WithContext;
use seal_fhe::{Ciphertext as SealCiphertext, Plaintext as SealPlaintext};
use serde::{Deserialize, Serialize};
use sunscreen_zkp_backend::BigInt;
pub use builder::*;
pub use error::*;
pub use keys::*;
#[cfg(feature = "linkedproofs")]
pub use linked::*;
pub use metadata::*;
pub use run::*;
pub use runtime::*;
pub use serialization::WithContext;
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize, Eq)]
/**
* The underlying backend implementation of a plaintext (e.g. SEAL's [`Plaintext`](seal_fhe::Plaintext)).
@@ -128,7 +128,7 @@ impl From<SealPlaintext> for SealData {
}
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
/**
* Represents an encoded plaintext suitable for use in the underlying scheme.
*/
@@ -165,6 +165,18 @@ pub enum InnerCiphertext {
Seal(Vec<WithContext<SealCiphertext>>),
}
impl InnerCiphertext {
/**
* Unwraps the enum and returns the underlying seal ciphertexts, or
* returns an error if this ciphertext isn't a Seal ciphertext.
*/
pub fn as_seal_ciphertext(&self) -> Result<&[WithContext<SealCiphertext>]> {
match self {
Self::Seal(d) => Ok(d),
}
}
}
#[derive(Clone, Deserialize, Serialize)]
/**
* An encryption of the given data type. Note, the data type is
@@ -183,6 +195,16 @@ pub struct Ciphertext {
pub inner: InnerCiphertext,
}
impl Ciphertext {
/**
* Unwraps the inner ciphertext as a Seal ciphertext variant. Returns an
* error if the inner ciphertext is not a Seal ciphertext.
*/
pub fn inner_as_seal_ciphertext(&self) -> Result<&[WithContext<SealCiphertext>]> {
self.inner.as_seal_ciphertext()
}
}
/**
* A trait that denotes this type can be used as an
* argument to an FHE program.
@@ -227,6 +249,12 @@ where
}
}
impl TypeNameInstance for ZkpProgramInput {
fn type_name_instance(&self) -> Type {
self.0.type_name_instance()
}
}
impl TypeNameInstance for FheProgramInput {
fn type_name_instance(&self) -> Type {
match self {
@@ -261,6 +289,12 @@ pub trait TryIntoPlaintext {
fn try_into_plaintext(&self, params: &Params) -> Result<Plaintext>;
}
impl TryIntoPlaintext for Plaintext {
fn try_into_plaintext(&self, _params: &Params) -> Result<Plaintext> {
Ok(self.clone())
}
}
/**
* A trait for converting values into fields used by ZKPs.
*/
@@ -330,3 +364,10 @@ pub trait TypeNameInstance {
*/
fn type_name_instance(&self) -> Type;
}
// Useful impl if you are aggregating a list of various types.
impl TypeNameInstance for Type {
fn type_name_instance(&self) -> Type {
self.clone()
}
}

View File

@@ -1,33 +1,35 @@
// For tests, see the sunscreen crate.
use std::{ops::Range, time::Instant};
use bitvec::vec::BitVec;
use bulletproofs::{BulletproofGens, GeneratorsChain, PedersenGens};
use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar};
use logproof::{crypto::CryptoHash, math::ModSwitch, LogProofVerifierKnowledge, ProofError};
use log::trace;
use logproof::{
math::rand256,
rings::{ZqSeal128_1024, ZqSeal128_2048, ZqSeal128_4096, ZqSeal128_8192},
InnerProductVerifierKnowledge, LogProof, LogProofGenerators, LogProofProverKnowledge,
LogProofVerifierKnowledge, ProofError,
};
use merlin::Transcript;
use sunscreen_math::ring::{Ring, RingModulus};
use paste::paste;
use seq_macro::seq;
use sunscreen_compiler_common::Type;
use sunscreen_zkp_backend::{
bulletproofs::{
BulletproofProverParameters, BulletproofVerifierParameters, BulletproofsBackend,
},
BigInt, CompiledZkpProgram, Proof, ZkpBackend,
BigInt, Proof, ZkpBackend,
};
use logproof::{
math::rand256, rings::ZqRistretto, LatticeProblem, LogProof, LogProofGenerators,
LogProofProverKnowledge,
};
use crate::{ZkpProgramInput, ZkpRuntime};
use crate::{CompiledZkpProgram, Result, TypeNameInstance, ZkpProgramInput, ZkpRuntime};
#[derive(Debug, Clone)]
/// SDLP proof and associated information for verification
pub struct Sdlp<Q>
where
Q: Ring + CryptoHash + ModSwitch<ZqRistretto> + RingModulus<4> + Ord + Clone,
{
pub struct Sdlp {
proof: LogProof,
vk: LogProofVerifierKnowledge<Q>,
vk: SealSdlpVerifierKnowledge,
g: Vec<RistrettoPoint>,
h: Vec<RistrettoPoint>,
u: RistrettoPoint,
@@ -41,53 +43,24 @@ struct BP {
}
#[derive(Clone)]
/// Linked proof between a SDLP and R1CS BP
pub struct LinkedProof<Q>
where
Q: Ring + CryptoHash + ModSwitch<ZqRistretto> + RingModulus<4> + Ord + Clone,
{
sdlp: Sdlp<Q>,
/// A linked proof between an SDLP and R1CS BP
pub struct LinkedProof {
sdlp: Sdlp,
bp: BP,
}
/// Errors that can occur when generating a linked SDLP and R1CS BP proof
#[derive(Debug, Clone, thiserror::Error)]
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum LinkedProofError {
/// An error with the ZKP proving.
#[error(transparent)]
ZkpError(sunscreen_zkp_backend::Error),
/// An error generating the runtime.
#[error(transparent)]
SunscreenRuntimeError(crate::Error),
/// Error from the SDLP.
#[error("SDLP proof error: {0:?}")]
LogproofProofError(ProofError),
LogproofProofError(#[from] ProofError),
/// The commitment to the shared inputs in the SDLP and R1CS BP do not match.
#[error("Shared commitments are not equal")]
SharedCommitmentsNotEqual,
}
impl From<sunscreen_zkp_backend::Error> for LinkedProofError {
fn from(err: sunscreen_zkp_backend::Error) -> Self {
LinkedProofError::ZkpError(err)
}
}
impl From<crate::Error> for LinkedProofError {
fn from(err: crate::Error) -> Self {
LinkedProofError::SunscreenRuntimeError(err)
}
}
impl From<ProofError> for LinkedProofError {
fn from(err: ProofError) -> Self {
LinkedProofError::LogproofProofError(err)
}
}
/// Generate a set of generators for a single party where some of the
/// generators are shared with another proof system.
fn new_single_party_with_shared_generators(
@@ -127,72 +100,64 @@ fn new_single_party_with_shared_generators(
BulletproofGens::new_from_generators(vec![g], vec![h]).unwrap()
}
impl<Q: Ring + CryptoHash + ModSwitch<ZqRistretto> + RingModulus<4> + Ord> LinkedProof<Q> {
/**
* 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.
*
* 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
*/
impl LinkedProof {
const TRANSCRIPT_LABEL: &'static [u8] = b"linked-sdlp-and-r1cs-bp";
/// 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
/// * `shared_types`: The types of the shared values (which need not be the same length as the
/// indices)
/// * `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>(
lattice_problem: &LatticeProblem<Q>,
prover_knowledge: &SealSdlpProverKnowledge,
shared_indices: &[(usize, usize)],
shared_types: &[Type],
program: &CompiledZkpProgram,
private_inputs: Vec<I>,
public_inputs: Vec<I>,
constant_inputs: Vec<I>,
) -> Result<Self, sunscreen_zkp_backend::Error>
) -> Result<Self>
where
I: Into<ZkpProgramInput> + Clone,
{
type Rt = ZkpRuntime<BulletproofsBackend>;
let backend = BulletproofsBackend::new();
let mut transcript = Transcript::new(b"linked-sdlp-and-r1cs-bp");
let mut transcript = Transcript::new(Self::TRANSCRIPT_LABEL);
let pk = LogProofProverKnowledge::new(
&lattice_problem.a,
&lattice_problem.s,
&lattice_problem.t,
&lattice_problem.b,
&lattice_problem.f,
);
let binary_parts = shared_indices
let vk = prover_knowledge.vk();
let shared_inputs_binary = shared_indices
.iter()
.map(|(i, j)| pk.s_binary_by_index((*i, *j)))
.map(|(i, j)| prover_knowledge.s_binary_by_index((*i, *j)))
.collect::<Vec<BitVec>>();
let gens = LogProofGenerators::new(pk.vk.l() as usize);
let gens = LogProofGenerators::new(vk.l() as usize);
// Get shared generators
let b_slices = pk.vk.b_slices();
let shared_gens = shared_indices
let b_slices = vk.b_slices();
let shared_gen_ranges = shared_indices
.iter()
.flat_map(|(i, j)| {
let range = (b_slices[*i][*j]).clone();
gens.h[range].to_vec()
})
.collect::<Vec<RistrettoPoint>>();
.map(|(i, j)| b_slices[*i][*j].clone())
.collect::<Vec<_>>();
let shared_gens = shared_gen_ranges
.iter()
.flat_map(|range| gens.h[range.clone()].to_vec())
.collect::<Vec<_>>();
let u = PedersenGens::default().B_blinding;
let half_rho = Scalar::from_bits(rand256());
let sdlp_proof = LogProof::create_with_shared(
let sdlp_proof = prover_knowledge.create_shared_logproof(
&mut transcript,
&pk,
&gens.g,
&gens.h,
&u,
@@ -202,54 +167,43 @@ impl<Q: Ring + CryptoHash + ModSwitch<ZqRistretto> + RingModulus<4> + Ord> Linke
let sdlp_package = Sdlp {
proof: sdlp_proof,
vk: pk.vk,
vk,
g: gens.g,
h: gens.h,
u,
};
let private_inputs_zkp_input: Vec<ZkpProgramInput> = private_inputs
.iter()
.map(|input| I::into(input.clone()))
.collect::<Vec<_>>();
let public_inputs_zkp_input: Vec<ZkpProgramInput> = public_inputs
.iter()
.map(|input| I::into(input.clone()))
.collect::<Vec<_>>();
let constant_inputs_zkp_input: Vec<ZkpProgramInput> = constant_inputs
.iter()
.map(|input| I::into(input.clone()))
.collect::<Vec<_>>();
// Convert inputs into bigints
let [private_inputs_bigint, public_inputs_bigint, constant_inputs_bigint] =
<Rt>::collect_zkp_args_with(
[private_inputs, public_inputs, constant_inputs],
|inputs| {
let mut all_types = shared_types.to_owned();
all_types.extend(inputs.concat().into_iter().map(|i| i.type_name_instance()));
<Rt>::validate_arguments(&program.metadata.signature, &all_types)
},
)?;
let private_inputs_bigint: Vec<BigInt> = private_inputs_zkp_input
.iter()
.flat_map(|input| input.0.to_native_fields())
.collect::<Vec<_>>();
let public_inputs_bigint: Vec<BigInt> = public_inputs_zkp_input
.iter()
.flat_map(|input| input.0.to_native_fields())
.collect::<Vec<_>>();
let constant_inputs_bigint: Vec<BigInt> = constant_inputs_zkp_input
.iter()
.flat_map(|input| input.0.to_native_fields())
.collect::<Vec<_>>();
// Convert sharted inputs into bigints
let shared_inputs_bigint = shared_inputs_binary.iter().flat_map(|shared_input_binary| {
shared_input_binary.iter().map(|y| BigInt::from(*y as u8))
});
// Prepend the bigint representations of our binary bits
let private_inputs_bigint = binary_parts
.iter()
.flat_map(|x| x.iter().map(|y| BigInt::from(*y as u64)))
// Combine the shared & private inputs
// Note: shared inputs _must_ come first. The proof linking logic depends on this.
let private_inputs_bigint = shared_inputs_bigint
.chain(private_inputs_bigint)
.collect::<Vec<_>>();
let metrics = backend.metrics(
program,
&program.zkp_program_fn,
&private_inputs_bigint,
&public_inputs_bigint,
&constant_inputs_bigint,
)?;
let constraint_count = backend.constraint_count(
program,
&program.zkp_program_fn,
&private_inputs_bigint,
&public_inputs_bigint,
&constant_inputs_bigint,
@@ -271,17 +225,23 @@ impl<Q: Ring + CryptoHash + ModSwitch<ZqRistretto> + RingModulus<4> + Ord> Linke
let prover_parameters =
BulletproofProverParameters::new(verifier_parameters.clone(), half_rho);
trace!("Starting BP JIT (prover)...");
let now = Instant::now();
let prog = backend.jit_prover(
program,
&program.zkp_program_fn,
&private_inputs_bigint,
&public_inputs_bigint,
&constant_inputs_bigint,
)?;
trace!("Prover BP JIT time {}s", now.elapsed().as_secs_f64());
let inputs = [public_inputs_bigint, private_inputs_bigint].concat();
trace!("Starting BP backend prove...");
let now = Instant::now();
let bp_proof =
backend.prove_with_parameters(&prog, &inputs, &prover_parameters, &mut transcript)?;
trace!("Prover BP time {}s", now.elapsed().as_secs_f64());
let bp_package = BP {
proof: bp_proof,
@@ -294,40 +254,39 @@ impl<Q: Ring + CryptoHash + ModSwitch<ZqRistretto> + RingModulus<4> + Ord> Linke
})
}
/**
* 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,
public_inputs: Vec<I>,
constant_inputs: Vec<I>,
) -> Result<(), LinkedProofError>
) -> Result<()>
where
I: Into<ZkpProgramInput> + Clone,
{
let runtime = ZkpRuntime::new(BulletproofsBackend::new())?;
let mut transcript = Transcript::new(b"linked-sdlp-and-r1cs-bp");
let mut transcript = Transcript::new(Self::TRANSCRIPT_LABEL);
self.sdlp.proof.verify(
&mut transcript,
&self.sdlp.vk,
&self.sdlp.g,
&self.sdlp.h,
&self.sdlp.u,
)?;
self.sdlp
.vk
.verify(
&self.sdlp.proof,
&mut transcript,
&self.sdlp.g,
&self.sdlp.h,
&self.sdlp.u,
)
.map_err(LinkedProofError::LogproofProofError)?;
runtime.verify_with_parameters(
program,
@@ -343,10 +302,216 @@ impl<Q: Ring + CryptoHash + ModSwitch<ZqRistretto> + RingModulus<4> + Ord> Linke
let a_i1_shared = (*b).0.A_I1_shared();
if a_i1_shared != self.sdlp.proof.w_shared.compress() {
return Err(LinkedProofError::SharedCommitmentsNotEqual);
return Err(LinkedProofError::SharedCommitmentsNotEqual.into());
}
}
Ok(())
}
}
impl Sdlp {
const TRANSCRIPT_LABEL: &'static [u8] = b"solo-sdlp";
/// This function creates a singular SDLP, not linked to any other proof system. This can be
/// used when only proving valid encryptions of known values, but _not_ for proving any
/// properties of those underlying values.
///
/// The [builder methods](`crate::LogProofBuilder`) offer an easier way to construct this proof.
pub fn create(prover_knowledge: &SealSdlpProverKnowledge) -> Result<Self> {
let mut transcript = Transcript::new(Self::TRANSCRIPT_LABEL);
let vk = prover_knowledge.vk();
let gen = LogProofGenerators::new(vk.l() as usize);
let u = InnerProductVerifierKnowledge::get_u();
let proof = prover_knowledge.create_logproof(&mut transcript, &gen.g, &gen.h, &u);
Ok(Self {
proof,
vk,
g: gen.g,
h: gen.h,
u,
})
}
/// This function verifies a solo SDLP.
pub fn verify(&self) -> Result<()> {
let mut transcript = Transcript::new(Self::TRANSCRIPT_LABEL);
self.vk
.verify(&self.proof, &mut transcript, &self.g, &self.h, &self.u)?;
Ok(())
}
}
/// The prover knowledge of an [`Sdlp`].
pub struct SealSdlpProverKnowledge(pub(crate) SealSdlpProverKnowledgeInternal);
/// The verifier knowledge of an [`Sdlp`].
#[derive(Debug, Clone)]
pub struct SealSdlpVerifierKnowledge(pub(crate) SealSdlpVerifierKnowledgeInternal);
pub(crate) enum SealSdlpProverKnowledgeInternal {
LP1(LogProofProverKnowledge<ZqSeal128_1024>),
LP2(LogProofProverKnowledge<ZqSeal128_2048>),
LP3(LogProofProverKnowledge<ZqSeal128_4096>),
LP4(LogProofProverKnowledge<ZqSeal128_8192>),
}
#[derive(Debug, Clone)]
pub(crate) enum SealSdlpVerifierKnowledgeInternal {
LP1(LogProofVerifierKnowledge<ZqSeal128_1024>),
LP2(LogProofVerifierKnowledge<ZqSeal128_2048>),
LP3(LogProofVerifierKnowledge<ZqSeal128_4096>),
LP4(LogProofVerifierKnowledge<ZqSeal128_8192>),
}
macro_rules! impl_from {
($zq_type:ty, $variant:ident) => {
paste! {
impl From<LogProofProverKnowledge<$zq_type>> for SealSdlpProverKnowledge {
fn from(k: LogProofProverKnowledge<$zq_type>) -> Self {
Self(SealSdlpProverKnowledgeInternal::$variant(k))
}
}
impl From<LogProofVerifierKnowledge<$zq_type>> for SealSdlpVerifierKnowledge {
fn from(k: LogProofVerifierKnowledge<$zq_type>) -> Self {
Self(SealSdlpVerifierKnowledgeInternal::$variant(k))
}
}
}
};
}
impl_from!(ZqSeal128_1024, LP1);
impl_from!(ZqSeal128_2048, LP2);
impl_from!(ZqSeal128_4096, LP3);
impl_from!(ZqSeal128_8192, LP4);
macro_rules! seq_zq {
($block:tt) => (
seq!(N in 1..=4 {
$block
})
)
}
impl SealSdlpProverKnowledge {
/// Get the binary expansion of a component of the witness matrix `S`.
///
/// Delegation to [`LogProofProverKnowledge::s_binary_by_index`].
pub fn s_binary_by_index(&self, index: (usize, usize)) -> BitVec {
seq_zq!({
match &self.0 {
#(
SealSdlpProverKnowledgeInternal::LP~N(pk) => pk.s_binary_by_index(index),
)*
}
})
}
/// Get the verifier knowledge component.
///
/// Delegation to [`LogProofProverKnowledge::vk`].
pub fn vk(&self) -> SealSdlpVerifierKnowledge {
seq_zq!({
match &self.0 {
#(
SealSdlpProverKnowledgeInternal::LP~N(pk) => {
SealSdlpVerifierKnowledge(SealSdlpVerifierKnowledgeInternal::LP~N(pk.vk.clone()))
}
)*
}
})
}
/// Create a shared `LogProof`.
///
/// Delegation to [`LogProof::create_with_shared`].
pub fn create_shared_logproof(
&self,
transcript: &mut Transcript,
g: &[RistrettoPoint],
h: &[RistrettoPoint],
u: &RistrettoPoint,
half_rho: &Scalar,
shared_indices: &[(usize, usize)],
) -> LogProof {
seq_zq!({
match &self.0 {
#(
SealSdlpProverKnowledgeInternal::LP~N(pk) => {
LogProof::create_with_shared(transcript, pk, g, h, u, half_rho, shared_indices)
}
)*
}
})
}
/// Create a `LogProof` without sharing.
///
/// Delegation to [`LogProof::create`].
pub fn create_logproof(
&self,
transcript: &mut Transcript,
g: &[RistrettoPoint],
h: &[RistrettoPoint],
u: &RistrettoPoint,
) -> LogProof {
seq_zq!({
match &self.0 {
#(
SealSdlpProverKnowledgeInternal::LP~N(pk) => LogProof::create(transcript, pk, g, h, u),
)*
}
})
}
}
impl SealSdlpVerifierKnowledge {
/// Get the length in bits of the binary expansion of the serialized secret * vectors.
///
/// Delegate to [`LogProofVerifierKnowledge::l`].
pub fn l(&self) -> u64 {
seq_zq!({
match &self.0 {
#(
SealSdlpVerifierKnowledgeInternal::LP~N(vk) => vk.l(),
)*
}
})
}
/// Get the ranges in the serialized coefficients of `S` corresponding to the bounds
///
/// Delegate to [`LogProofVerifierKnowledge::b_slices`].
pub fn b_slices(&self) -> Vec<Vec<Range<usize>>> {
seq_zq!({
match &self.0 {
#(
SealSdlpVerifierKnowledgeInternal::LP~N(vk) => vk.b_slices(),
)*
}
})
}
/// Verify the log proof.
///
/// Delegate to [`LogProof::verify`].
pub fn verify(
&self,
logproof: &LogProof,
transcript: &mut Transcript,
g: &[RistrettoPoint],
h: &[RistrettoPoint],
u: &RistrettoPoint,
) -> Result<(), ProofError> {
seq_zq!({
match &self.0 {
#(
SealSdlpVerifierKnowledgeInternal::LP~N(vk) => logproof.verify(transcript, vk, g, h, u),
)*
}
})
}
}

View File

@@ -4,6 +4,7 @@ pub use semver::Version;
use serde::{Deserialize, Serialize};
use sunscreen_compiler_common::Type;
use sunscreen_fhe_program::{FheProgram, SchemeType};
use sunscreen_zkp_backend::CompiledZkpProgram as ZkpProgram;
use crate::{Error, Result};
@@ -200,7 +201,7 @@ pub struct FheProgramMetadata {
*/
pub struct CompiledFheProgram {
/**
* The underlying FHE FHE program.
* The underlying FHE program.
*/
pub fhe_program_fn: FheProgram,
@@ -211,6 +212,29 @@ pub struct CompiledFheProgram {
pub metadata: FheProgramMetadata,
}
/// A serializable list of metadata for a ZKP program.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ZkpProgramMetadata {
/// The FHE scheme parameters, required for ZKP programs with SDLP-linked inputs.
pub params: Option<Params>,
/// The call signature (arguments and returns) of the ZKP program.
pub signature: CallSignature,
}
/**
* A ZKP program with its associated metadata.
*/
#[derive(Clone)]
pub struct CompiledZkpProgram {
/// The underlying ZKP program.
pub zkp_program_fn: ZkpProgram,
/// Information about the FHE program, including its call signature and the scheme
/// parameters needed for ZKP programs with SDLP-linked inputs.
pub metadata: ZkpProgramMetadata,
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -5,11 +5,13 @@ use merlin::Transcript;
use crate::error::*;
use crate::metadata::*;
use crate::ProofBuilder;
use crate::VerificationBuilder;
use crate::ZkpProgramInput;
use crate::{
run_program_unchecked, serialization::WithContext, Ciphertext, FheProgramInput,
InnerCiphertext, InnerPlaintext, Plaintext, PrivateKey, PublicKey, SealCiphertext, SealData,
SealPlaintext, TryFromPlaintext, TryIntoPlaintext, TypeNameInstance,
run_program_unchecked, serialization::WithContext, Ciphertext, CompiledZkpProgram,
FheProgramInput, InnerCiphertext, InnerPlaintext, Plaintext, PrivateKey, PublicKey,
SealCiphertext, SealData, SealPlaintext, TryFromPlaintext, TryIntoPlaintext, TypeNameInstance,
};
use log::trace;
@@ -23,7 +25,6 @@ use seal_fhe::{
pub use sunscreen_compiler_common::{Type, TypeName};
use sunscreen_zkp_backend::BigInt;
use sunscreen_zkp_backend::CompiledZkpProgram;
use sunscreen_zkp_backend::Proof;
use sunscreen_zkp_backend::ZkpBackend;
@@ -114,10 +115,10 @@ pub mod marker {
*/
#[allow(unused)]
pub struct BFVEncryptionComponents {
ciphertext: Ciphertext,
u: Vec<PolynomialArray>,
e: Vec<PolynomialArray>,
r: Vec<Plaintext>,
pub(crate) ciphertext: Ciphertext,
pub(crate) u: Vec<PolynomialArray>,
pub(crate) e: Vec<PolynomialArray>,
pub(crate) r: Vec<Plaintext>,
}
/**
@@ -181,6 +182,30 @@ pub struct GenericRuntime<T, B> {
_phantom_t: PhantomData<T>,
zkp_backend: B,
}
impl<T, B> GenericRuntime<T, B> {
pub(crate) fn validate_arguments<A>(signature: &CallSignature, arguments: &[A]) -> Result<()>
where
A: TypeNameInstance,
{
if arguments.len() != signature.arguments.len()
|| arguments
.iter()
.map(|a| a.type_name_instance())
.zip(signature.arguments.iter())
.any(|(ty, expected)| ty != *expected)
{
Err(Error::argument_mismatch(
&signature.arguments,
&arguments
.iter()
.map(|a| a.type_name_instance())
.collect::<Vec<_>>(),
))
} else {
Ok(())
}
}
}
impl<T, B> GenericRuntime<T, B>
where
@@ -327,6 +352,16 @@ where
&fhe_data.params
}
/**
* Returns the underlying SEAL context.
*/
#[allow(unused)]
pub(crate) fn context(&self) -> &SealContext {
match &self.runtime_data.unwrap_fhe().context {
Context::Seal(seal_ctx) => seal_ctx,
}
}
/**
* Validates and runs the given FHE program. Unless you can guarantee your FHE program is valid,
* you should use this method rather than [`run_program_unchecked`].
@@ -358,27 +393,8 @@ where
let mut arguments: Vec<FheProgramInput> = arguments.drain(0..).map(|a| a.into()).collect();
let expected_args = &fhe_program.metadata.signature.arguments;
// Check the arguments match the signature.
if expected_args.len() != arguments.len() {
return Err(Error::IncorrectCiphertextCount);
}
// Check the passed arguments' types match the signature.
if arguments
.iter()
.enumerate()
.any(|(i, a)| a.type_name_instance() != expected_args[i])
{
return Err(Error::argument_mismatch(
expected_args,
&arguments
.iter()
.map(|a| a.type_name_instance())
.collect::<Vec<Type>>(),
));
}
Self::validate_arguments(&fhe_program.metadata.signature, &arguments)?;
if fhe_program.metadata.signature.num_ciphertexts.len()
!= fhe_program.metadata.signature.returns.len()
@@ -504,7 +520,7 @@ where
* Returns [`Error::ParameterMismatch`] if the plaintext is incompatible
* with this runtime's scheme.
*/
fn encrypt_return_components<P>(
pub(crate) fn encrypt_return_components<P>(
&self,
val: P,
public_key: &PublicKey,
@@ -549,7 +565,7 @@ where
* Returns [`Error::ParameterMismatch`] if the plaintext is incompatible with this runtime's
* scheme.
*/
fn encrypt_return_components_switched<P>(
pub(crate) fn encrypt_return_components_switched<P>(
&self,
val: P,
public_key: &PublicKey,
@@ -559,11 +575,27 @@ where
where
P: TryIntoPlaintext + TypeName,
{
let plaintext = val.try_into_plaintext(self.params())?;
let plaintext_type = P::type_name();
self.encrypt_return_components_switched_internal(
&plaintext,
&plaintext_type,
public_key,
export_components,
seed,
)
}
pub(crate) fn encrypt_return_components_switched_internal(
&self,
plaintext: &Plaintext,
plaintext_type: &Type,
public_key: &PublicKey,
export_components: bool,
seed: Option<&[u64; 8]>,
) -> Result<BFVEncryptionComponents> {
let fhe_data = self.runtime_data.unwrap_fhe();
let plaintext = val.try_into_plaintext(&fhe_data.params)?;
let (ciphertext, u, e, r) = match (&fhe_data.context, plaintext.inner) {
let (ciphertext, u, e, r) = match (&fhe_data.context, &plaintext.inner) {
(Context::Seal(context), InnerPlaintext::Seal(inner_plain)) => {
let encryptor = Encryptor::with_public_key(context, &public_key.public_key.data)?;
@@ -579,7 +611,7 @@ where
let ciphertexts = inner_plain
.iter()
.map(|p| {
let ciphertext = if export_components {
let ciphertext = if !export_components {
encryptor.encrypt(p).map_err(Error::SealError)
} else {
let (ciphertext, u, e, r) = encrypt_function(&encryptor, p, seed)?;
@@ -590,7 +622,7 @@ where
};
let r = Plaintext {
data_type: P::type_name(),
data_type: plaintext_type.clone(),
inner: InnerPlaintext::Seal(vec![r_context]),
};
@@ -615,7 +647,7 @@ where
Ciphertext {
data_type: Type {
is_encrypted: true,
..P::type_name()
..plaintext_type.clone()
},
inner: InnerCiphertext::Seal(ciphertexts),
},
@@ -640,6 +672,52 @@ where
T: marker::Zkp,
B: ZkpBackend,
{
/// Take each input type and transform into bigints. Optionally validate args with provided closure.
pub(crate) fn collect_zkp_args_with<const N: usize, I>(
args: [Vec<I>; N],
predicate: impl FnOnce(&[Vec<ZkpProgramInput>]) -> Result<()>,
) -> Result<[Vec<BigInt>; N]>
where
I: Into<ZkpProgramInput>,
{
// Collect into inputs
let inputs = args.map(|xs| xs.into_iter().map(I::into).collect::<Vec<_>>());
// Validate inputs
predicate(&inputs)?;
// Collect into bigints
let bigints = inputs.map(|xs| {
xs.into_iter()
.flat_map(|x| x.0.to_native_fields())
.collect()
});
Ok(bigints)
}
/// Collect all ZKP arg types into bigints and validate against program call signature.
pub(crate) fn collect_and_validate_zkp_args<const N: usize, I>(
args: [Vec<I>; N],
program: &CompiledZkpProgram,
) -> Result<[Vec<BigInt>; N]>
where
I: Into<ZkpProgramInput>,
{
Self::collect_zkp_args_with(args, |inputs: &[Vec<ZkpProgramInput>]| {
let all_inputs = inputs.concat();
Self::validate_arguments(&program.metadata.signature, &all_inputs)
})
}
/// Collect ZKP arg types into bigints with no validation. Useful for verification side, as our
/// call signature currently isn't granular enough to cover linked/private/public/constant.
pub(crate) fn collect_zkp_args<const N: usize, I>(args: [Vec<I>; N]) -> Result<[Vec<BigInt>; N]>
where
I: Into<ZkpProgramInput>,
{
Self::collect_zkp_args_with(args, |_| Ok(()))
}
/**
* Prove the given `inputs` satisfy `program`.
*/
@@ -653,18 +731,10 @@ where
where
I: Into<ZkpProgramInput>,
{
let private_inputs = private_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let public_inputs = public_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let constant_inputs = constant_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let [private_inputs, public_inputs, constant_inputs] = Self::collect_and_validate_zkp_args(
[private_inputs, public_inputs, constant_inputs],
program,
)?;
let backend = &self.zkp_backend;
@@ -672,8 +742,12 @@ where
let now = Instant::now();
let prog =
backend.jit_prover(program, &private_inputs, &public_inputs, &constant_inputs)?;
let prog = backend.jit_prover(
&program.zkp_program_fn,
&private_inputs,
&public_inputs,
&constant_inputs,
)?;
trace!("Prover JIT time {}s", now.elapsed().as_secs_f64());
@@ -684,53 +758,6 @@ where
Ok(backend.prove(&prog, &inputs)?)
}
/**
* Prove the given `inputs` satisfy `program` with parameters for that
* particular proof system.
*/
pub fn prove_with_parameters<I>(
&self,
program: &CompiledZkpProgram,
private_inputs: Vec<I>,
public_inputs: Vec<I>,
constant_inputs: Vec<I>,
parameters: &B::ProverParameters,
transcript: &mut Transcript,
) -> Result<Proof>
where
I: Into<ZkpProgramInput>,
{
let private_inputs = private_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let public_inputs = public_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let constant_inputs = constant_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let backend = &self.zkp_backend;
trace!("Starting JIT (prover)...");
let now = Instant::now();
let prog =
backend.jit_prover(program, &private_inputs, &public_inputs, &constant_inputs)?;
trace!("Prover JIT time {}s", now.elapsed().as_secs_f64());
let inputs = [public_inputs, private_inputs].concat();
trace!("Starting backend prove...");
Ok(backend.prove_with_parameters(&prog, &inputs, parameters, transcript)?)
}
/// Create a proof builder.
///
/// This provides a wrapper around calling [`Self::prove`], and can be convenient when you
@@ -766,14 +793,8 @@ where
where
I: Into<ZkpProgramInput>,
{
let constant_inputs = constant_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let public_inputs = public_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let [public_inputs, constant_inputs] =
Self::collect_zkp_args([public_inputs, constant_inputs])?;
let backend = &self.zkp_backend;
@@ -781,7 +802,8 @@ where
let now = Instant::now();
let prog = backend.jit_verifier(program, &constant_inputs, &public_inputs)?;
let prog =
backend.jit_verifier(&program.zkp_program_fn, &constant_inputs, &public_inputs)?;
trace!("Verifier JIT time {}s", now.elapsed().as_secs_f64());
trace!("Starting backend verify...");
@@ -792,7 +814,7 @@ where
/**
* Verify that the given `proof` satisfies the given `program`.
*/
pub fn verify_with_parameters<I>(
pub(crate) fn verify_with_parameters<I>(
&self,
program: &CompiledZkpProgram,
proof: &Proof,
@@ -804,14 +826,8 @@ where
where
I: Into<ZkpProgramInput>,
{
let constant_inputs = constant_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let public_inputs = public_inputs
.into_iter()
.flat_map(|x| I::into(x).0.to_native_fields())
.collect::<Vec<BigInt>>();
let [public_inputs, constant_inputs] =
Self::collect_zkp_args([public_inputs, constant_inputs])?;
let backend = &self.zkp_backend;
@@ -819,7 +835,8 @@ where
let now = Instant::now();
let prog = backend.jit_verifier(program, &constant_inputs, &public_inputs)?;
let prog =
backend.jit_verifier(&program.zkp_program_fn, &constant_inputs, &public_inputs)?;
trace!("Verifier JIT time {}s", now.elapsed().as_secs_f64());
trace!("Starting backend verify...");
@@ -997,203 +1014,3 @@ impl<B> ZkpRuntime<B> {
* can do both.
*/
pub type Runtime = GenericRuntime<(), ()>;
/// A builder for creating a proof.
///
/// This is offered as a convenience for building the arguments necessary for the
/// [`prove`][GenericRuntime::prove] function.
pub struct ProofBuilder<'r, 'p, T: marker::Zkp, B: ZkpBackend> {
runtime: &'r GenericRuntime<T, B>,
program: &'p CompiledZkpProgram,
private_inputs: Vec<ZkpProgramInput>,
public_inputs: Vec<ZkpProgramInput>,
constant_inputs: Vec<ZkpProgramInput>,
}
impl<'r, 'p, T: marker::Zkp, B: ZkpBackend> ProofBuilder<'r, 'p, T, B> {
/// Create a new `ProofBuilder`. It's typically more convenient to create a proof builder
/// via [`runtime.proof_builder()`][GenericRuntime::proof_builder].
pub fn new(runtime: &'r GenericRuntime<T, B>, program: &'p CompiledZkpProgram) -> Self
where
T: marker::Zkp,
B: ZkpBackend,
{
Self {
runtime,
program,
private_inputs: vec![],
public_inputs: vec![],
constant_inputs: vec![],
}
}
/// Add a constant input to the proof builder.
pub fn constant_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.constant_inputs.push(input.into());
self
}
/// Add multiple constant inputs to the proof builder.
pub fn constant_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.constant_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Add a public input to the proof builder.
pub fn public_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.public_inputs.push(input.into());
self
}
/// Add multiple public inputs to the proof builder.
pub fn public_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.public_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Add a private input to the proof builder.
pub fn private_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.private_inputs.push(input.into());
self
}
/// Add multiple private inputs to the proof builder.
pub fn private_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.private_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Generate a proof; see [`runtime.prove()`][GenericRuntime::prove].
pub fn prove(self) -> Result<Proof> {
self.runtime.prove(
self.program,
self.private_inputs,
self.public_inputs,
self.constant_inputs,
)
}
/// Generate a proof with parameters for that proof system; see
/// [`runtime.prove_with_parameters()`][GenericRuntime::prove_with_parameters].
pub fn prove_with_parameters(
self,
parameters: &B::ProverParameters,
transcript: &mut Transcript,
) -> Result<Proof> {
self.runtime.prove_with_parameters(
self.program,
self.private_inputs,
self.public_inputs,
self.constant_inputs,
parameters,
transcript,
)
}
}
/// A builder for verifying a proof.
///
/// This is offered as a convenience for building the arguments necessary for the
/// [`verify`][GenericRuntime::verify] function.
pub struct VerificationBuilder<'r, 'p, 'a, T: marker::Zkp, B: ZkpBackend> {
runtime: &'r GenericRuntime<T, B>,
program: &'p CompiledZkpProgram,
proof: Option<&'a Proof>,
constant_inputs: Vec<ZkpProgramInput>,
public_inputs: Vec<ZkpProgramInput>,
}
impl<'r, 'p, 'a, T: marker::Zkp, B: ZkpBackend> VerificationBuilder<'r, 'p, 'a, T, B> {
/// Create a new `VerificationBuilder`. It's typically more convenient to create a
/// verification builder via
/// [`runtime.verification_builder()`][GenericRuntime::verification_builder].
pub fn new(runtime: &'r GenericRuntime<T, B>, program: &'p CompiledZkpProgram) -> Self
where
T: marker::Zkp,
B: ZkpBackend,
{
Self {
runtime,
program,
proof: None,
public_inputs: vec![],
constant_inputs: vec![],
}
}
/// Add the proof to verify.
pub fn proof(mut self, proof: &'a Proof) -> Self {
self.proof = Some(proof);
self
}
/// Add a constant input to the verification builder.
pub fn constant_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.constant_inputs.push(input.into());
self
}
/// Add multiple constant inputs to the verification builder.
pub fn constant_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.constant_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Add a public input to the verification builder.
pub fn public_input(mut self, input: impl Into<ZkpProgramInput>) -> Self {
self.public_inputs.push(input.into());
self
}
/// Add multiple public inputs to the verification builder.
pub fn public_inputs<I>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
ZkpProgramInput: From<T>,
{
self.public_inputs
.extend(inputs.into_iter().map(ZkpProgramInput::from));
self
}
/// Verify that `self.proof` satisfies `self.program`; see
/// [`runtime.verify()`][GenericRuntime::verify].
///
/// # Remarks
/// Will error if the underlying `verify` call errors, or if a proof has not yet been
/// supplied to the builder. That is, you must call [`Self::proof`] before calling this
/// function.
pub fn verify(self) -> Result<()> {
let proof = self.proof.ok_or_else(|| {
Error::zkp_builder_error(
"You must supply a proof to the verification builder before calling `verify`",
)
})?;
self.runtime.verify(
self.program,
proof,
self.public_inputs,
self.constant_inputs,
)
}
}

View File

@@ -16,7 +16,7 @@ mod jit;
use std::{
any::Any,
ops::{Add, Deref, Mul, Neg, Sub},
ops::{Add, Deref, Mul, Neg, Shl, Sub},
};
pub use crypto_bigint::Uint;
@@ -361,6 +361,18 @@ impl BigInt {
pub const ONE: Self = Self(U512::ONE);
}
impl Shl<usize> for BigInt {
type Output = BigInt;
/// NOTE: this operation is variable time with respect to `rhs` *ONLY*.
///
/// When used with a fixed `rhs`, this function is constant-time with respect
/// to `self`.
fn shl(self, rhs: usize) -> BigInt {
BigInt::from(self.0.shl(rhs))
}
}
/**
* The methods needed for a type to serve as a proof
* system in the Sunscreen ecosystem.