diff --git a/Cargo.lock b/Cargo.lock index e51dfb13d..4de7a1502 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.21.7" @@ -745,6 +751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "rand_core", + "serdect", "subtle", ] @@ -2237,7 +2244,6 @@ name = "private_tx_linkedproof" version = "0.1.0" dependencies = [ "env_logger", - "once_cell", "sunscreen", ] @@ -2714,6 +2720,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha2" version = "0.9.9" @@ -3014,6 +3030,7 @@ dependencies = [ "ocl", "rand", "rayon", + "serde", "statrs", "subtle", "sunscreen_curve25519", diff --git a/Cargo.toml b/Cargo.toml index 25fef1547..83bbf0980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ tokio = { version = "1.25.0", features = ["rt"] } wgpu = "0.17.0" cust = "0.3.2" num = "0.4.1" -crypto-bigint = "0.5.2" +crypto-bigint = { version = "0.5.2", features = ["serde"] } paste = "1.0.14" thiserror = "1.0.44" naga = { version = "0.13.0", features = ["wgsl-in"] } diff --git a/examples/private_tx_linkedproof/Cargo.toml b/examples/private_tx_linkedproof/Cargo.toml index 09b9faa4c..3b93cff52 100644 --- a/examples/private_tx_linkedproof/Cargo.toml +++ b/examples/private_tx_linkedproof/Cargo.toml @@ -6,4 +6,3 @@ edition = "2021" [dependencies] sunscreen = { path = "../../sunscreen", features = ["linkedproofs"] } env_logger = { workspace = true } -once_cell = { workspace = true } diff --git a/examples/private_tx_linkedproof/src/main.rs b/examples/private_tx_linkedproof/src/main.rs index a8cfc17e8..c7f39db36 100644 --- a/examples/private_tx_linkedproof/src/main.rs +++ b/examples/private_tx_linkedproof/src/main.rs @@ -3,13 +3,12 @@ //! where deterministic computation is performed on encrypted data. The linked proofs allow us to //! validate the encrypted inputs. -use std::collections::{hash_map::Entry, HashMap}; +use std::collections::HashMap; -use once_cell::sync::Lazy; use sunscreen::{ bulletproofs::BulletproofsBackend, fhe_program, - linked::{LinkedProof, LogProofBuilder}, + linked::LinkedProof, types::{ bfv::Signed, zkp::{ @@ -19,21 +18,27 @@ use sunscreen::{ Cipher, }, zkp_program, zkp_var, Ciphertext, CompiledFheProgram, CompiledZkpProgram, Compiler, - FheZkpApplication, FheZkpRuntime, Params, PrivateKey, PublicKey, Result, ZkpProgramInput, + FheProgramInput, FheZkpApplication, FheZkpRuntime, Params, PrivateKey, PublicKey, Result, }; /// Subtract the transaction amount from the sender's balance. #[fhe_program(scheme = "bfv")] -fn update_balance_sender(balance: Cipher, tx: Cipher) -> Cipher { +fn transfer_from(balance: Cipher, tx: Cipher) -> Cipher { balance - tx } /// Add the transaction amount to the receiver's balance. #[fhe_program(scheme = "bfv")] -fn update_balance_receiver(balance: Cipher, tx: Cipher) -> Cipher { +fn transfer_to(balance: Cipher, tx: Cipher) -> Cipher { balance + tx } +/// Add the public transaction amount to a user's balance. +#[fhe_program(scheme = "bfv")] +fn deposit_to(balance: Cipher, deposit: Signed) -> Cipher { + balance + deposit +} + /// Validate a transfer transaction. #[zkp_program] fn validate_transfer( @@ -49,10 +54,10 @@ fn validate_transfer( tx.constrain_le_bounded(sender_balance, 64); } -/// Validate deposit/registration. The deposit amount is public, but we must prove that the -/// provided ciphertext encrypts the deposit amount. +/// Validate registration. The deposit amount is public, but we must prove that the provided +/// ciphertext encrypts the deposit amount. #[zkp_program] -fn validate_deposit( +fn validate_registration( #[linked] encrypted_deposit: BfvSigned, #[public] public_deposit: Field, ) { @@ -109,7 +114,7 @@ impl User { receiver: U, ) -> Result { let receiver = receiver.into(); - let mut builder = LogProofBuilder::new(&self.runtime); + let mut builder = self.runtime.linkedproof_builder(); // Encrypt tx amount under sender's public key. println!(" {}: encrypting {} under own key", self.name, amount); @@ -123,7 +128,7 @@ impl User { self.name, amount ); let recv_pk = chain.keys.get(&receiver).unwrap(); - let encrypted_amount_receiver = builder.encrypt_msg(&amount_linked, recv_pk)?; + let encrypted_amount_receiver = builder.reencrypt(&amount_linked, recv_pk)?; // Decrypt current balance, needed to prove tx validity let balance_enc = chain.balances.get(&self.name).unwrap(); @@ -139,7 +144,7 @@ impl User { .zkp_program(self.app.get_transfer_zkp())? .linked_input(amount_linked) .linked_input(balance_linked) - .build_linkedproof()?; + .build()?; Ok(Transfer { proof, @@ -151,33 +156,16 @@ impl User { } /// Create a public deposit to a private balance. - pub fn create_deposit(&self, amount: i64) -> Result { - let mut builder = LogProofBuilder::new(&self.runtime); - - // Encrypt deposit amount - println!(" {}: encrypting and linking {}", self.name, amount); - let (amount_enc, amount_linked) = - builder.encrypt_returning_link(&Signed::from(amount), &self.public_key)?; - - // Create deposit proof - println!(" {}: creating deposit linkedproof", self.name); - let proof = builder - .zkp_program(self.app.get_deposit_zkp())? - .linked_input(amount_linked) - .public_input(BulletproofsField::from(amount)) - .build_linkedproof()?; - - Ok(Deposit { - proof, - encrypted_amount: amount_enc, + pub fn create_deposit(&self, amount: i64) -> Deposit { + Deposit { public_amount: amount, name: self.name.clone(), - }) + } } /// Create a refresh balance transaction. pub fn create_refresh_balance(&self, chain: &Chain) -> Result { - let mut builder = LogProofBuilder::new(&self.runtime); + let mut builder = self.runtime.linkedproof_builder(); // Decrypt current balance, returning a link to the underlying message let balance_encrypted = chain.balances.get(&self.name).unwrap(); @@ -191,12 +179,12 @@ impl User { // Generate proof that the ciphertexts encrypt the same underlying message and that // the new one has a fresh noise budget and fresh encoding. - println!(" {}: creating refresh balance logproof", self.name); + println!(" {}: creating refresh balance linkedproof", self.name); let proof = builder .zkp_program(self.app.get_refresh_balance_zkp())? .linked_input(existing_link) .linked_input(fresh_link) - .build_linkedproof()?; + .build()?; Ok(RefreshBalance { proof, @@ -206,29 +194,48 @@ impl User { } pub fn create_register(&self, initial_deposit: i64) -> Result { - let deposit = self.create_deposit(initial_deposit)?; + let mut builder = self.runtime.linkedproof_builder(); + + // Encrypt deposit amount + println!( + " {}: encrypting and linking {}", + self.name, initial_deposit + ); + let (amount_enc, amount_linked) = + builder.encrypt_returning_link(&Signed::from(initial_deposit), &self.public_key)?; + + // Create registration proof + println!(" {}: creating registration linkedproof", self.name); + let proof = builder + .zkp_program(self.app.get_registration_zkp())? + .linked_input(amount_linked) + .public_input(BulletproofsField::from(initial_deposit)) + .build()?; + Ok(Register { + proof, + encrypted_amount: amount_enc, public_key: self.public_key.clone(), - deposit, + deposit: self.create_deposit(initial_deposit), }) } } -/// A register transaction is an initial deposit plus an identifying public key. -#[derive(Clone)] -pub struct Register { - public_key: PublicKey, - deposit: Deposit, -} - -/// A deposit transaction. +/// A register transaction. /// /// The SDLP in the linked proof proves that the ciphertext is a valid, fresh encryption. The R1CS /// ZKP in the linked proof proves that the amount encrypted matches the public amount deposited. #[derive(Clone)] -pub struct Deposit { +pub struct Register { proof: LinkedProof, + public_key: PublicKey, encrypted_amount: Ciphertext, + deposit: Deposit, +} + +/// A public deposit transaction. +#[derive(Clone)] +pub struct Deposit { public_amount: i64, name: Username, } @@ -305,51 +312,47 @@ impl Chain { pub fn register(&mut self, register: Register) -> Result<()> { self.ledger.push(Transaction::Register(register.clone())); let Register { + proof, + encrypted_amount, public_key, deposit, } = register; + // First, verify that the encrypted amount matches the public amount + let mut builder = self.runtime.linkedproof_verification_builder(); + builder.encrypt_returning_link::(&encrypted_amount, &public_key)?; + builder + .zkp_program(self.app.get_registration_zkp())? + .proof(proof) + .public_input(BulletproofsField::from(deposit.public_amount)) + .verify()?; + + // Register the user's public key self.keys.insert(deposit.name.clone(), public_key); - self.deposit(deposit) + + // Set the initial encrypted balance + self.balances.insert(deposit.name, encrypted_amount); + Ok(()) } pub fn deposit(&mut self, deposit: Deposit) -> Result<()> { self.ledger.push(Transaction::Deposit(deposit.clone())); let Deposit { - proof, - encrypted_amount, public_amount, name, } = deposit; - // First, verify that the encrypted amount matches the public amount - proof.verify( - self.app.get_deposit_zkp(), - vec![BulletproofsField::from(public_amount)], - vec![], - )?; - - // Then deposit into the user's balance + // Deposit into the user's balance let pk = self.keys.get(&name).unwrap(); - match self.balances.entry(name) { - // Update existing balance - Entry::Occupied(mut entry) => { - let curr = entry.get().clone(); - let updated = self - .runtime - .run( - self.app.get_update_balance_receiver_fhe(), - vec![curr, encrypted_amount], - pk, - )? - .remove(0); - entry.insert(updated); - } - // Or insert the amount if this is an initial deposit - Entry::Vacant(entry) => { - entry.insert(encrypted_amount); - } - } + let curr_bal = self.balances.get_mut(&name).unwrap(); + *curr_bal = self + .runtime + .run::( + self.app.get_deposit_to_fhe(), + vec![curr_bal.clone().into(), Signed::from(public_amount).into()], + pk, + )? + .remove(0); Ok(()) } @@ -364,33 +367,45 @@ impl Chain { } = transfer; // First verify the transfer is valid - proof.verify::(self.app.get_transfer_zkp(), vec![], vec![])?; + let mut builder = self.runtime.linkedproof_verification_builder(); + let link = builder.encrypt_returning_link::( + &encrypted_amount_sender, + self.keys.get(&sender).unwrap(), + )?; + builder.reencrypt( + &link, + &encrypted_amount_receiver, + self.keys.get(&receiver).unwrap(), + )?; + builder.decrypt_returning_link::(self.balances.get(&sender).unwrap())?; + builder + .zkp_program(self.app.get_transfer_zkp())? + .proof(proof) + .verify()?; // Update the sender's balance: let sender_pk = self.keys.get(&sender).unwrap(); let sender_balance = self.balances.get_mut(&sender).unwrap(); - let new_balance = self + *sender_balance = self .runtime .run( - self.app.get_update_balance_sender_fhe(), + self.app.get_transfer_from_fhe(), vec![sender_balance.clone(), encrypted_amount_sender], sender_pk, )? .remove(0); - *sender_balance = new_balance; // Update receiver's balance let receiver_pk = self.keys.get(&receiver).unwrap(); let receiver_balance = self.balances.get_mut(&receiver).unwrap(); - let new_balance = self + *receiver_balance = self .runtime .run( - self.app.get_update_balance_receiver_fhe(), + self.app.get_transfer_to_fhe(), vec![receiver_balance.clone(), encrypted_amount_receiver], receiver_pk, )? .remove(0); - *receiver_balance = new_balance; Ok(()) } @@ -404,7 +419,13 @@ impl Chain { } = refresh_balance; // Verify the balance refresh is valid - proof.verify::(self.app.get_refresh_balance_zkp(), vec![], vec![])?; + let mut builder = self.runtime.linkedproof_verification_builder(); + builder.decrypt_returning_link::(self.balances.get(&name).unwrap())?; + builder.encrypt_returning_link::(&fresh_balance, self.keys.get(&name).unwrap())?; + builder + .zkp_program(self.app.get_refresh_balance_zkp())? + .proof(proof) + .verify()?; // Use the freshly encrypted balance self.balances @@ -432,53 +453,55 @@ impl Chain { } } -pub struct App(&'static Lazy); +pub struct App(FheZkpApplication); impl App { pub fn new() -> Result { - static APP: Lazy = Lazy::new(|| { - Compiler::new() - .fhe_program(update_balance_sender) - .fhe_program(update_balance_receiver) - // These params are not necessary to run the example, but they do shave a few - // minutes off the runtime. In practice, you probably want to use the default - // parameters provided by the compiler. The ones set here will result in balances - // needing to be refreshed more often. - .with_params(&Params { - lattice_dimension: 1024, - coeff_modulus: vec![0x7e00001], - plain_modulus: 512, - scheme_type: sunscreen::SchemeType::Bfv, - security_level: sunscreen::SecurityLevel::TC128, - }) - .zkp_backend::() - .zkp_program(validate_transfer) - .zkp_program(validate_deposit) - .zkp_program(validate_refresh_balance) - .compile() - .unwrap() - }); - Ok(Self(&APP)) + let app = Compiler::new() + .fhe_program(transfer_to) + .fhe_program(transfer_from) + .fhe_program(deposit_to) + // These params are not necessary to run the example, but they do shave a few + // minutes off the runtime. In practice, you probably want to use the default + // parameters provided by the compiler. The ones set here will result in balances + // needing to be refreshed more often. + .with_params(&Params { + lattice_dimension: 1024, + coeff_modulus: vec![0x7e00001], + plain_modulus: 512, + scheme_type: sunscreen::SchemeType::Bfv, + security_level: sunscreen::SecurityLevel::TC128, + }) + .zkp_backend::() + .zkp_program(validate_transfer) + .zkp_program(validate_registration) + .zkp_program(validate_refresh_balance) + .compile()?; + Ok(Self(app)) } pub fn get_transfer_zkp(&self) -> &CompiledZkpProgram { self.0.get_zkp_program(validate_transfer).unwrap() } - pub fn get_deposit_zkp(&self) -> &CompiledZkpProgram { - self.0.get_zkp_program(validate_deposit).unwrap() + pub fn get_registration_zkp(&self) -> &CompiledZkpProgram { + self.0.get_zkp_program(validate_registration).unwrap() } pub fn get_refresh_balance_zkp(&self) -> &CompiledZkpProgram { self.0.get_zkp_program(validate_refresh_balance).unwrap() } - pub fn get_update_balance_sender_fhe(&self) -> &CompiledFheProgram { - self.0.get_fhe_program(update_balance_sender).unwrap() + pub fn get_transfer_to_fhe(&self) -> &CompiledFheProgram { + self.0.get_fhe_program(transfer_to).unwrap() } - pub fn get_update_balance_receiver_fhe(&self) -> &CompiledFheProgram { - self.0.get_fhe_program(update_balance_receiver).unwrap() + pub fn get_transfer_from_fhe(&self) -> &CompiledFheProgram { + self.0.get_fhe_program(transfer_from).unwrap() + } + + pub fn get_deposit_to_fhe(&self) -> &CompiledFheProgram { + self.0.get_fhe_program(deposit_to).unwrap() } pub fn params(&self) -> &Params { @@ -501,7 +524,7 @@ fn main() -> Result<()> { chain.register(alice.create_register(deposit)?)?; let deposit = 50; println!("Depositing an extra {deposit}"); - chain.deposit(alice.create_deposit(deposit)?)?; + chain.deposit(alice.create_deposit(deposit))?; println!(); diff --git a/logproof/src/linear_algebra.rs b/logproof/src/linear_algebra.rs index eb57dded8..044d214cf 100644 --- a/logproof/src/linear_algebra.rs +++ b/logproof/src/linear_algebra.rs @@ -3,6 +3,7 @@ use bitvec::slice::BitSlice; use curve25519_dalek::scalar::Scalar; use digest::Digest; use rayon::prelude::*; +use serde::{Deserialize, Serialize}; use std::borrow::Borrow; use std::ops::{Add, Div, Index, IndexMut, Mul, Sub}; use sunscreen_math::field::Field; @@ -14,7 +15,7 @@ use crate::crypto::CryptoHash; use crate::math::{ModSwitch, Tensor}; use crate::rings::{FieldFrom, ZqRistretto}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] /** * An `m x n` matrix of elements. * diff --git a/logproof/src/linear_relation.rs b/logproof/src/linear_relation.rs index cc9c23556..f5c5dc127 100644 --- a/logproof/src/linear_relation.rs +++ b/logproof/src/linear_relation.rs @@ -52,7 +52,7 @@ type MatrixPoly = Matrix>; /** * Bounds on the coefficients in the secret S (specified in number of bits). */ -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bounds(pub Vec); impl Zero for Bounds { @@ -76,7 +76,7 @@ impl std::ops::Deref for Bounds { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] /// The artifacts known to both the prover and verifier. pub struct VerifierKnowledge where diff --git a/sunscreen/Cargo.toml b/sunscreen/Cargo.toml index e1b620a74..009b0b542 100644 --- a/sunscreen/Cargo.toml +++ b/sunscreen/Cargo.toml @@ -30,6 +30,7 @@ rustdoc-args = ["--html-in-header", "docs/assets/katex-header.html"] bumpalo = { workspace = true } crypto-bigint = { workspace = true } log = { workspace = true } +logproof = { workspace = true, optional = true } num = { workspace = true } paste = { workspace = true } petgraph = { workspace = true } @@ -64,7 +65,7 @@ serde_json = { workspace = true } [features] bulletproofs = ["sunscreen_zkp_backend/bulletproofs"] hexl = ["seal_fhe/hexl"] -linkedproofs = ["bulletproofs", "sunscreen_runtime/linkedproofs"] +linkedproofs = ["bulletproofs", "sunscreen_runtime/linkedproofs", "logproof"] transparent-ciphertexts = ["seal_fhe/transparent-ciphertexts"] deterministic = ["seal_fhe/deterministic", "sunscreen_runtime/deterministic"] diff --git a/sunscreen/src/linked.rs b/sunscreen/src/linked.rs index 7c87e7b8e..b08867c4a 100644 --- a/sunscreen/src/linked.rs +++ b/sunscreen/src/linked.rs @@ -19,11 +19,14 @@ //! 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 +//! How does this work in practice? If you use our [builder](`LinkedProofBuilder`), 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 logproof::Bounds; pub use sunscreen_runtime::{ - ExistingMessage, LinkWithZkp, LinkedMessage, LinkedProof, LogProofBuilder, Message, Sdlp, + ExistingMessage, LinkWithZkp, LinkedMessage, LinkedProof, LinkedProofBuilder, + LinkedProofVerificationBuilder, Message, MessageRef, Sdlp, SdlpBuilder, + SdlpVerificationBuilder, }; diff --git a/sunscreen/src/types/zkp/field.rs b/sunscreen/src/types/zkp/field.rs index 96a595f4c..9b12db80c 100644 --- a/sunscreen/src/types/zkp/field.rs +++ b/sunscreen/src/types/zkp/field.rs @@ -53,52 +53,56 @@ impl Field { * Converts a big-endian hex string into a native field. */ pub fn from_be_hex(hex_str: &str) -> Self { - Self { - val: BigInt::from_be_hex(hex_str), - _phantom: PhantomData, - } + from_unsigned(BigInt::from_be_hex(hex_str)) } } impl From for Field { - fn from(val: BigInt) -> Self { - Self { - val, - _phantom: PhantomData, - } + fn from(x: BigInt) -> Self { + from_unsigned(x) } } impl From for Field { fn from(x: u8) -> Self { - (u64::from(x)).into() + from_unsigned(x) } } impl From for Field { fn from(x: u16) -> Self { - (u64::from(x)).into() + from_unsigned(x) } } impl From for Field { fn from(x: u32) -> Self { - (u64::from(x)).into() + from_unsigned(x) } } impl From for Field { fn from(x: u64) -> Self { - assert!(F::FIELD_MODULUS != BigInt::ZERO); + from_unsigned(x) + } +} - let m = NonZero::from_uint(*F::FIELD_MODULUS); +impl From for Field { + fn from(x: u128) -> Self { + from_unsigned(x) + } +} - // unwrap is okay here as we've ensured FIELD_MODULUS is - // non-zero. - Self { - val: BigInt::from(BigInt::from(x).rem(&m)), - _phantom: PhantomData, - } +fn from_unsigned, F: FieldSpec>(x: T) -> Field { + assert!(F::FIELD_MODULUS != BigInt::ZERO); + + let m = NonZero::from_uint(*F::FIELD_MODULUS); + + // unwrap is okay here as we've ensured FIELD_MODULUS is + // non-zero. + Field { + val: BigInt::from(x.into().rem(&m)), + _phantom: PhantomData, } } diff --git a/sunscreen/tests/linked.rs b/sunscreen/tests/linked.rs index 15eb32283..9a0a978b2 100644 --- a/sunscreen/tests/linked.rs +++ b/sunscreen/tests/linked.rs @@ -15,7 +15,9 @@ mod linked_tests { zkp_program, zkp_var, Compiler, }; use sunscreen_fhe_program::SchemeType; - use sunscreen_runtime::{FheZkpRuntime, LogProofBuilder, Params, ZkpProgramInput}; + use sunscreen_runtime::{ + FheZkpRuntime, LinkedProofBuilder, LinkedProofVerificationBuilder, Params, + }; use sunscreen_zkp_backend::bulletproofs::BulletproofsBackend; lazy_static! { @@ -63,9 +65,9 @@ mod linked_tests { // Try valid cases for tx in [5, 10] { - let mut proof_builder = LogProofBuilder::new(&rt); + let mut proof_builder = LinkedProofBuilder::new(&rt); - let (_ct, tx_msg) = proof_builder + let (ct, tx_msg) = proof_builder .encrypt_returning_link(&Signed::from(tx), &public_key) .unwrap(); @@ -75,17 +77,22 @@ mod linked_tests { .unwrap() .linked_input(tx_msg) .public_input(BulletproofsField::from(balance)) - .build_linkedproof() + .build() .unwrap(); println!("Linked proof done"); println!("Performing linked verify"); - lp.verify( - valid_transaction_zkp, - vec![BulletproofsField::from(balance)], - vec![], - ) - .expect("Failed to verify linked proof"); + let mut verify_builder = LinkedProofVerificationBuilder::new(&rt); + verify_builder + .encrypt_returning_link::(&ct, &public_key) + .unwrap(); + verify_builder + .proof(lp) + .zkp_program(valid_transaction_zkp) + .unwrap() + .public_input(BulletproofsField::from(balance)) + .verify() + .expect("Failed to verify linked proof"); println!("Linked verify done"); } } @@ -107,7 +114,7 @@ mod linked_tests { let balance = 10i64; for tx in [-1, balance + 1] { - let mut proof_builder = LogProofBuilder::new(&rt); + let mut proof_builder = LinkedProofBuilder::new(&rt); let (_ct, tx_msg) = proof_builder .encrypt_returning_link(&Signed::from(tx), &public_key) .unwrap(); @@ -117,7 +124,7 @@ mod linked_tests { .linked_input(tx_msg) .public_input(BulletproofsField::from(balance)); - let lp = proof_builder.build_linkedproof(); + let lp = proof_builder.build(); assert!(lp.is_err()); } } @@ -142,8 +149,8 @@ mod linked_tests { 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 + let mut proof_builder = LinkedProofBuilder::new(&rt); + let (ct, val_msg) = proof_builder .encrypt_returning_link(&Signed::from(val), &public_key) .unwrap(); proof_builder @@ -152,7 +159,7 @@ mod linked_tests { .linked_input(val_msg) .public_input(BulletproofsField::from(val)); - let lp = proof_builder.build_linkedproof().unwrap_or_else(|e| { + let lp = proof_builder.build().unwrap_or_else(|e| { panic!( "Failed to encode {} value; {e}", if val.is_positive() { @@ -162,7 +169,16 @@ mod linked_tests { } ) }); - lp.verify(is_eq_zkp, vec![BulletproofsField::from(val)], vec![]) + let mut verify_builder = LinkedProofVerificationBuilder::new(&rt); + verify_builder + .encrypt_returning_link::(&ct, &public_key) + .unwrap(); + verify_builder + .proof(lp) + .zkp_program(is_eq_zkp) + .unwrap() + .public_input(BulletproofsField::from(val)) + .verify() .expect("Failed to verify linked proof"); } } @@ -197,8 +213,8 @@ mod linked_tests { // ensure denominator is positive let x_d = rand::random::().saturating_abs().saturating_add(1); let x = Rational::from(Rational64::new_raw(x_n, x_d)); - let mut proof_builder = LogProofBuilder::new(&rt); - let (_ct, x_msg) = proof_builder + let mut proof_builder = LinkedProofBuilder::new(&rt); + let (ct, x_msg) = proof_builder .encrypt_returning_link(&x, &public_key) .unwrap(); proof_builder @@ -209,9 +225,18 @@ mod linked_tests { .private_input(BulletproofsField::from(x_d)); let lp = proof_builder - .build_linkedproof() + .build() .unwrap_or_else(|e| panic!("Failed to prove encoding of {x:?}; {e}")); - lp.verify::(is_eq_zkp, vec![], vec![]) + + let mut verify_builder = LinkedProofVerificationBuilder::new(&rt); + verify_builder + .encrypt_returning_link::(&ct, &public_key) + .unwrap(); + verify_builder + .proof(lp) + .zkp_program(is_eq_zkp) + .unwrap() + .verify() .unwrap_or_else(|e| panic!("Failed to verify encoding of {x:?}; {e}")); } } @@ -243,11 +268,11 @@ mod linked_tests { let y = rand::random::() as i64; let (x, y) = (Signed::from(x.min(y)), Signed::from(x.max(y))); - let mut proof_builder = LogProofBuilder::new(&rt); - let (_ct, x_msg) = proof_builder + let mut proof_builder = LinkedProofBuilder::new(&rt); + let (x_ct, x_msg) = proof_builder .encrypt_returning_link(&x, &public_key) .unwrap(); - let (_ct, y_msg) = proof_builder + let (y_ct, y_msg) = proof_builder .encrypt_symmetric_returning_link(&y, &private_key) .unwrap(); proof_builder @@ -257,9 +282,21 @@ mod linked_tests { .linked_input(y_msg); let lp = proof_builder - .build_linkedproof() + .build() .unwrap_or_else(|e| panic!("Failed to prove {x:?} <= {y:?}, {e}")); - lp.verify::(compare_signed_zkp, vec![], vec![]) + + let mut verify_builder = LinkedProofVerificationBuilder::new(&rt); + verify_builder + .encrypt_returning_link::(&x_ct, &public_key) + .unwrap(); + verify_builder + .encrypt_symmetric_returning_link::(&y_ct) + .unwrap(); + verify_builder + .proof(lp) + .zkp_program(compare_signed_zkp) + .unwrap() + .verify() .unwrap_or_else(|e| panic!("Failed to verify {x:?} <= {y:?}; {e}")); } } @@ -298,11 +335,11 @@ mod linked_tests { let y = Rational64::new_raw(y_n, y_d); let (x, y) = (Rational::from(x.min(y)), Rational::from(x.max(y))); - let mut proof_builder = LogProofBuilder::new(&rt); - let (_ct, x_msg) = proof_builder + let mut proof_builder = LinkedProofBuilder::new(&rt); + let (x_ct, x_msg) = proof_builder .encrypt_returning_link(&x, &public_key) .unwrap(); - let (_ct, y_msg) = proof_builder + let (y_ct, y_msg) = proof_builder .encrypt_returning_link(&y, &public_key) .unwrap(); proof_builder @@ -312,9 +349,21 @@ mod linked_tests { .linked_input(y_msg); let lp = proof_builder - .build_linkedproof() + .build() .unwrap_or_else(|e| panic!("Failed to prove {x:?} <= {y:?}, {e}")); - lp.verify::(compare_rational_zkp, vec![], vec![]) + + let mut verify_builder = LinkedProofVerificationBuilder::new(&rt); + verify_builder + .encrypt_returning_link::(&x_ct, &public_key) + .unwrap(); + verify_builder + .encrypt_returning_link::(&y_ct, &public_key) + .unwrap(); + verify_builder + .proof(lp) + .zkp_program(compare_rational_zkp) + .unwrap() + .verify() .unwrap_or_else(|e| panic!("Failed to verify {x:?} <= {y:?}; {e}")); } } @@ -348,14 +397,14 @@ mod linked_tests { let (public_key, private_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 + let mut proof_builder = LinkedProofBuilder::new(&rt); + let (x_ct, x_msg) = proof_builder .encrypt_returning_link(&Signed::from(val), &public_key) .unwrap(); // proves same plaintext within SDLP - let _ct_x1 = proof_builder.encrypt_msg(&x_msg, &public_key).unwrap(); + let x1_ct = proof_builder.reencrypt(&x_msg, &public_key).unwrap(); // proves same value within ZKP - let (_ct_y, y_msg) = proof_builder + let (y_ct, y_msg) = proof_builder .encrypt_symmetric_returning_link(&Signed::from(val), &private_key) .unwrap(); proof_builder @@ -365,8 +414,23 @@ mod linked_tests { .linked_input(y_msg) .private_input(BulletproofsField::from(val)); - let lp = proof_builder.build_linkedproof().unwrap(); - lp.verify::(is_eq_zkp, vec![], vec![]) + let lp = proof_builder.build().unwrap(); + + let mut verify_builder = LinkedProofVerificationBuilder::new(&rt); + let x_msg = verify_builder + .encrypt_returning_link::(&x_ct, &public_key) + .unwrap(); + verify_builder + .reencrypt(&x_msg, &x1_ct, &public_key) + .unwrap(); + verify_builder + .encrypt_symmetric_returning_link::(&y_ct) + .unwrap(); + verify_builder + .proof(lp) + .zkp_program(is_eq_zkp) + .unwrap() + .verify() .expect("Failed to verify linked proof"); } } @@ -397,11 +461,11 @@ mod linked_tests { // ensure denominator is positive let y_d = rand::random::().saturating_abs().saturating_add(1); let y = Rational::from(Rational64::new_raw(y_n, y_d)); - let mut proof_builder = LogProofBuilder::new(&rt); - let (_ct, x_msg) = proof_builder + let mut proof_builder = LinkedProofBuilder::new(&rt); + let (x_ct, x_msg) = proof_builder .encrypt_returning_link(&x, &public_key) .unwrap(); - let (_ct, y_msg) = proof_builder + let (y_ct, y_msg) = proof_builder .encrypt_returning_link(&y, &public_key) .unwrap(); let lp = proof_builder @@ -409,11 +473,23 @@ mod linked_tests { .unwrap() .linked_input(x_msg) .linked_input(y_msg) - .build_linkedproof() + .build() .unwrap_or_else(|e| { panic!("Failed to prove fresh encodings of {x:?} and {y:?}; Error: {e}") }); - lp.verify::(is_fresh_zkp, vec![], vec![]) + + let mut verify_builder = LinkedProofVerificationBuilder::new(&rt); + verify_builder + .encrypt_returning_link::(&x_ct, &public_key) + .unwrap(); + verify_builder + .encrypt_returning_link::(&y_ct, &public_key) + .unwrap(); + verify_builder + .proof(lp) + .zkp_program(is_fresh_zkp) + .unwrap() + .verify() .unwrap_or_else(|e| { panic!("Failed to verify fresh encodings of {x:?} and {y:?}; Error: {e}") }); @@ -479,7 +555,7 @@ mod linked_tests { .remove(0); } - let mut proof_builder = LogProofBuilder::new(&rt); + let mut proof_builder = LinkedProofBuilder::new(&rt); let (_, x_msg) = proof_builder .decrypt_returning_link::(&x_ct, &private_key) .unwrap(); @@ -491,7 +567,7 @@ mod linked_tests { .unwrap() .linked_input(x_msg) .linked_input(y_msg) - .build_linkedproof(); + .build(); let (x, y) = if ix % 2 == 0 { (format!("double({x:?})"), format!("{y:?}")) } else { @@ -533,7 +609,7 @@ mod linked_tests { // but use runtime with modulus 4096 let rt = FheZkpRuntime::new(&TEST_PARAMS, &BulletproofsBackend::new()).unwrap(); - let mut proof_builder = LogProofBuilder::new(&rt); + let mut proof_builder = LinkedProofBuilder::new(&rt); let res = proof_builder.zkp_program(is_eq_zkp); assert!(matches!( res, @@ -555,7 +631,7 @@ mod linked_tests { 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); + let mut proof_builder = LinkedProofBuilder::new(&rt); proof_builder.zkp_program(is_eq_zkp).unwrap(); for _ in 0..num_linked_inputs { let (_ct, msg) = proof_builder @@ -566,7 +642,7 @@ mod linked_tests { for _ in 0..num_private_inputs { proof_builder.private_input(BulletproofsField::from(1)); } - let res = proof_builder.build_linkedproof(); + let res = proof_builder.build(); assert!(matches!( res, Err(sunscreen::RuntimeError::ArgumentMismatch(_)) @@ -591,7 +667,7 @@ mod linked_tests { let compare_signed_zkp = app.get_zkp_program(compare_signed).unwrap(); let (public_key, _secret_key) = rt.generate_keys().unwrap(); - let mut proof_builder = LogProofBuilder::new(&rt); + let mut proof_builder = LinkedProofBuilder::new(&rt); proof_builder.zkp_program(compare_signed_zkp).unwrap(); let (_ct, signed_msg) = proof_builder .encrypt_returning_link(&Signed::from(1), &public_key) @@ -602,7 +678,7 @@ mod linked_tests { proof_builder .linked_input(signed_msg) .linked_input(unsigned_msg); - let res = proof_builder.build_linkedproof(); + let res = proof_builder.build(); assert!(matches!( res, Err(sunscreen::RuntimeError::ArgumentMismatch(_)) diff --git a/sunscreen/tests/sdlp.rs b/sunscreen/tests/sdlp.rs index bb8a61ebe..a19f9be76 100644 --- a/sunscreen/tests/sdlp.rs +++ b/sunscreen/tests/sdlp.rs @@ -9,7 +9,7 @@ mod sdlp_tests { use sunscreen_compiler_macros::fhe_program; use sunscreen_fhe_program::SchemeType; - use sunscreen_runtime::{FheRuntime, LogProofBuilder, Params}; + use sunscreen_runtime::{FheRuntime, Params, SdlpBuilder, SdlpVerificationBuilder}; lazy_static! { static ref TEST_PARAMS: Params = Params { @@ -25,75 +25,96 @@ mod sdlp_tests { fn prove_one_asymmetric_statement() { let rt = FheRuntime::new(&TEST_PARAMS).unwrap(); let (public_key, _secret_key) = rt.generate_keys().unwrap(); - let mut logproof_builder = LogProofBuilder::new(&rt); + let mut logproof_builder = SdlpBuilder::new(&rt); - let _ct = logproof_builder + let ct = logproof_builder .encrypt(&Signed::from(3), &public_key) .unwrap(); - let sdlp = logproof_builder.build_logproof().unwrap(); - sdlp.verify().unwrap(); + let sdlp = logproof_builder.build().unwrap(); + + let mut logproof_vk_builder = SdlpVerificationBuilder::new(&rt); + logproof_vk_builder.encrypt(&ct, &public_key).unwrap(); + logproof_vk_builder.proof(sdlp).verify().unwrap(); } #[test] fn prove_one_symmetric_statement() { let rt = FheRuntime::new(&TEST_PARAMS).unwrap(); let (_public_key, private_key) = rt.generate_keys().unwrap(); - let mut logproof_builder = LogProofBuilder::new(&rt); + let mut logproof_builder = SdlpBuilder::new(&rt); - let _ct = logproof_builder + let ct = logproof_builder .encrypt_symmetric(&Unsigned64::from(3), &private_key) .unwrap(); - let sdlp = logproof_builder.build_logproof().unwrap(); - sdlp.verify().unwrap(); + let sdlp = logproof_builder.build().unwrap(); + + let mut logproof_vk_builder = SdlpVerificationBuilder::new(&rt); + logproof_vk_builder.encrypt_symmetric(&ct).unwrap(); + logproof_vk_builder.proof(sdlp).verify().unwrap(); } #[test] fn prove_linked_statements() { let rt = FheRuntime::new(&TEST_PARAMS).unwrap(); let (public_key, private_key) = rt.generate_keys().unwrap(); - let mut logproof_builder = LogProofBuilder::new(&rt); + let mut logproof_builder = SdlpBuilder::new(&rt); - let (_a1, linked_a) = logproof_builder + let (a1, linked_a) = logproof_builder .encrypt_returning_msg(&Fractional::<64>::from(3.23), &public_key) .unwrap(); - let _a2 = logproof_builder - .encrypt_symmetric_msg(&linked_a, &private_key) + let a2 = logproof_builder + .reencrypt_symmetric(&linked_a, &private_key) .unwrap(); - let _other = logproof_builder + let other = logproof_builder .encrypt(&Signed::from(2), &public_key) .unwrap(); - let sdlp = logproof_builder.build_logproof().unwrap(); - sdlp.verify().unwrap(); + + let sdlp = logproof_builder.build().unwrap(); + + let mut logproof_vk_builder = SdlpVerificationBuilder::new(&rt); + let linked_a = logproof_vk_builder + .encrypt_returning_msg(&a1, &public_key) + .unwrap(); + logproof_vk_builder + .reencrypt_symmetric(&linked_a, &a2) + .unwrap(); + logproof_vk_builder.encrypt(&other, &public_key).unwrap(); + logproof_vk_builder.proof(sdlp).verify().unwrap(); } #[test] fn prove_refreshing_existing_ciphertext() { let rt = FheRuntime::new(&TEST_PARAMS).unwrap(); let (public_key, private_key) = rt.generate_keys().unwrap(); - let mut logproof_builder = LogProofBuilder::new(&rt); let initial_ct = rt.encrypt(Signed::from(100), &public_key).unwrap(); - #[fhe_program(scheme = "bfv")] fn double(x: sunscreen::types::Cipher) -> sunscreen::types::Cipher { x + x } - let double_compiled = double.compile().unwrap(); - let computed_ct = rt .run(&double_compiled, vec![initial_ct], &public_key) .unwrap() .remove(0); + let mut logproof_builder = SdlpBuilder::new(&rt); let (_, msg) = logproof_builder .decrypt_returning_msg::(&computed_ct, &private_key) .unwrap(); - let _refreshed_ct = logproof_builder.encrypt_msg(&msg, &public_key).unwrap(); + let refreshed_ct = logproof_builder.reencrypt(&msg, &public_key).unwrap(); - let sdlp = logproof_builder.build_logproof().unwrap(); - sdlp.verify().unwrap(); + let sdlp = logproof_builder.build().unwrap(); + + let mut logproof_vk_builder = SdlpVerificationBuilder::new(&rt); + let msg = logproof_vk_builder + .decrypt_returning_msg(&computed_ct) + .unwrap(); + logproof_vk_builder + .reencrypt(&msg, &refreshed_ct, &public_key) + .unwrap(); + logproof_vk_builder.proof(sdlp).verify().unwrap(); } } diff --git a/sunscreen_bulletproofs b/sunscreen_bulletproofs index fefc70bd9..1a0e4215d 160000 --- a/sunscreen_bulletproofs +++ b/sunscreen_bulletproofs @@ -1 +1 @@ -Subproject commit fefc70bd9f3126dab3e43e051f66bc4981fe1da5 +Subproject commit 1a0e4215df18a4aa71f4869802beee97741bf679 diff --git a/sunscreen_math/Cargo.toml b/sunscreen_math/Cargo.toml index af7e78fea..2eaa16283 100644 --- a/sunscreen_math/Cargo.toml +++ b/sunscreen_math/Cargo.toml @@ -29,6 +29,7 @@ cust = { workspace = true, optional = true } num = { workspace = true } crypto-bigint = { workspace = true } thiserror = { workspace = true } +serde = { workspace = true } sunscreen_math_macros = { workspace = true } subtle = { workspace = true } log.workspace = true diff --git a/sunscreen_math/src/poly/mod.rs b/sunscreen_math/src/poly/mod.rs index 2d2a9b6ef..e83523bbc 100644 --- a/sunscreen_math/src/poly/mod.rs +++ b/sunscreen_math/src/poly/mod.rs @@ -1,10 +1,11 @@ use std::ops::{Add, Index, IndexMut, Mul, Neg, Sub}; +use serde::{Deserialize, Serialize}; use sunscreen_math_macros::refify_binary_op; use crate::{ring::Ring, One, Zero}; -#[derive(Debug, Clone, Eq)] +#[derive(Debug, Clone, Eq, Serialize, Deserialize)] /// A polynomial over the ring `T`. /// /// # Remarks diff --git a/sunscreen_math/src/ring/mod.rs b/sunscreen_math/src/ring/mod.rs index f1bd6af0f..1e831dd83 100644 --- a/sunscreen_math/src/ring/mod.rs +++ b/sunscreen_math/src/ring/mod.rs @@ -3,6 +3,7 @@ use crypto_bigint::NonZero; pub use crypto_bigint::Uint; use curve25519_dalek::scalar::Scalar; use num::traits::{ToBytes, WrappingAdd, WrappingMul, WrappingNeg, WrappingSub}; +use serde::{Deserialize, Deserializer, Serialize}; use std::{ marker::PhantomData, ops::{Add, Mul, Neg, Sub}, @@ -405,6 +406,97 @@ impl> Clone for Zq { impl> Copy for Zq {} +impl> Serialize for Zq { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // Helper method that is valid when `N == M`, even though the compiler doesn't realize it. + fn cast>(x: &Zq) -> Uint { + Uint::::new(x.into_bigint().to_limbs()[..].try_into().unwrap()) + } + // Support up to 16 limbs (U1024 on 64-bit, U512 on 32-bit), and panic loudly if we + // ever exceed this. + // Note that on 32-bit builds, Uint impls the Encoding trait necessary for + // serialization only for even LIMBS. + match N { + #[cfg(target_pointer_width = "64")] + 1 => cast::<1, N, B>(self).serialize(serializer), + 2 => cast::<2, N, B>(self).serialize(serializer), + #[cfg(target_pointer_width = "64")] + 3 => cast::<3, N, B>(self).serialize(serializer), + 4 => cast::<4, N, B>(self).serialize(serializer), + #[cfg(target_pointer_width = "64")] + 5 => cast::<5, N, B>(self).serialize(serializer), + 6 => cast::<6, N, B>(self).serialize(serializer), + #[cfg(target_pointer_width = "64")] + 7 => cast::<7, N, B>(self).serialize(serializer), + 8 => cast::<8, N, B>(self).serialize(serializer), + #[cfg(target_pointer_width = "64")] + 9 => cast::<9, N, B>(self).serialize(serializer), + 10 => cast::<10, N, B>(self).serialize(serializer), + #[cfg(target_pointer_width = "64")] + 11 => cast::<11, N, B>(self).serialize(serializer), + 12 => cast::<12, N, B>(self).serialize(serializer), + #[cfg(target_pointer_width = "64")] + 13 => cast::<13, N, B>(self).serialize(serializer), + 14 => cast::<14, N, B>(self).serialize(serializer), + #[cfg(target_pointer_width = "64")] + 15 => cast::<15, N, B>(self).serialize(serializer), + 16 => cast::<16, N, B>(self).serialize(serializer), + _ => unimplemented!(), + } + } +} + +impl<'de, const N: usize, B: ArithmeticBackend> Deserialize<'de> for Zq { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Helper method that is valid when `N == M`, even though the compiler doesn't realize it. + fn cast<'de, const M: usize, const N: usize, B, D>(x: Uint) -> Result, D::Error> + where + B: ArithmeticBackend, + D: Deserializer<'de>, + { + Zq::try_from(Uint::::new(x.to_limbs()[..].try_into().unwrap())) + .map_err(serde::de::Error::custom) + } + // Support up to 16 limbs (U1024 on 64-bit, U512 on 32-bit), and panic loudly if we + // ever exceed this. + // Note that on 32-bit builds, Uint impls the Encoding trait necessary for + // deserialization only for even LIMBS. + match N { + #[cfg(target_pointer_width = "64")] + 1 => cast::<'_, 1, N, B, D>(Uint::deserialize(deserializer)?), + 2 => cast::<'_, 2, N, B, D>(Uint::deserialize(deserializer)?), + #[cfg(target_pointer_width = "64")] + 3 => cast::<'_, 3, N, B, D>(Uint::deserialize(deserializer)?), + 4 => cast::<'_, 4, N, B, D>(Uint::deserialize(deserializer)?), + #[cfg(target_pointer_width = "64")] + 5 => cast::<'_, 5, N, B, D>(Uint::deserialize(deserializer)?), + 6 => cast::<'_, 6, N, B, D>(Uint::deserialize(deserializer)?), + #[cfg(target_pointer_width = "64")] + 7 => cast::<'_, 7, N, B, D>(Uint::deserialize(deserializer)?), + 8 => cast::<'_, 8, N, B, D>(Uint::deserialize(deserializer)?), + #[cfg(target_pointer_width = "64")] + 9 => cast::<'_, 9, N, B, D>(Uint::deserialize(deserializer)?), + 10 => cast::<'_, 10, N, B, D>(Uint::deserialize(deserializer)?), + #[cfg(target_pointer_width = "64")] + 11 => cast::<'_, 11, N, B, D>(Uint::deserialize(deserializer)?), + 12 => cast::<'_, 12, N, B, D>(Uint::deserialize(deserializer)?), + #[cfg(target_pointer_width = "64")] + 13 => cast::<'_, 13, N, B, D>(Uint::deserialize(deserializer)?), + 14 => cast::<'_, 14, N, B, D>(Uint::deserialize(deserializer)?), + #[cfg(target_pointer_width = "64")] + 15 => cast::<'_, 15, N, B, D>(Uint::deserialize(deserializer)?), + 16 => cast::<'_, 16, N, B, D>(Uint::deserialize(deserializer)?), + _ => unimplemented!(), + } + } +} + #[refify_binary_op] impl Add<&Zq> for &Zq where diff --git a/sunscreen_runtime/src/builder.rs b/sunscreen_runtime/src/builder.rs index 755a50db0..15b7bd02c 100644 --- a/sunscreen_runtime/src/builder.rs +++ b/sunscreen_runtime/src/builder.rs @@ -206,7 +206,11 @@ impl<'r, 'p, 'a, T: marker::Zkp, B: ZkpBackend> VerificationBuilder<'r, 'p, 'a, } #[cfg(feature = "linkedproofs")] -pub use linked::{ExistingMessage, LinkWithZkp, LinkedMessage, LogProofBuilder, Message}; +pub use linked::{ + ExistingMessage, LinkWithZkp, LinkedMessage, LinkedProofBuilder, + LinkedProofVerificationBuilder, LogProofBuilder, LogProofVerificationBuilder, Message, + MessageRef, SdlpBuilder, SdlpVerificationBuilder, +}; #[cfg(feature = "linkedproofs")] mod linked { @@ -218,7 +222,7 @@ mod linked { bfv_statement::{self, BfvMessage, BfvProofStatement, BfvWitness, StatementParams}, math::Log2, rings::{SealQ128_1024, SealQ128_2048, SealQ128_4096, SealQ128_8192}, - Bounds, LogProofProverKnowledge, + Bounds, LogProofProverKnowledge, LogProofVerifierKnowledge, }; use sunscreen_compiler_common::{Type, TypeName}; use sunscreen_math::ring::{BarrettBackend, BarrettConfig, Zq}; @@ -228,9 +232,10 @@ mod linked { }; use crate::{ - marker, Ciphertext, CompiledZkpProgram, GenericRuntime, LinkedProof, NumCiphertexts, - Params, Plaintext, PrivateKey, PublicKey, Result, Sdlp, SealSdlpProverKnowledge, - TryFromPlaintext, TryIntoPlaintext, ZkpProgramInput, + marker, Ciphertext, CompiledZkpProgram, Fhe, FheRuntime, FheZkp, FheZkpRuntime, + GenericRuntime, LinkedProof, NumCiphertexts, Params, Plaintext, PrivateKey, PublicKey, + Result, Sdlp, SdlpProverKnowledge, SdlpVerifierKnowledge, TryFromPlaintext, + TryIntoPlaintext, ZkpProgramInput, }; /// All FHE plaintext types can be used in a [`Sdlp`]. This trait indicates further that a @@ -277,12 +282,12 @@ mod linked { zkp_type: Z, } - /// A [`Plaintext`] message that can be [encrypted again](`LogProofBuilder::encrypt_msg`). + /// A [`Plaintext`] message that can be [encrypted again](`LogProofBuilder::reencrypt`). #[derive(Clone, Debug)] pub struct Message(MessageInternal<()>); - /// A [`Plaintext`] message that can be [encrypted again](`LogProofBuilder::encrypt_msg`) or - /// [linked to a ZKP](`LogProofBuilder::linked_input`). Create this with + /// A [`Plaintext`] message that can be [encrypted again](`LogProofBuilder::reencrypt`) or + /// [linked to a ZKP program](`LogProofBuilder::linked_input`). Create this with /// [`LogProofBuilder::encrypt_returning_link`]. #[derive(Debug)] pub struct LinkedMessage(MessageInternal); @@ -306,7 +311,7 @@ mod linked { } /// Indicates that the message is already added to the SDLP, and hence can be used as an - /// argument to [`LogProofBuilder::encrypt_msg`]. + /// argument to [`LogProofBuilder::reencrypt`]. pub trait ExistingMessage: private::Sealed { /// Convert the message to the internal type. fn as_internal(&self) -> MessageInternal<()>; @@ -396,6 +401,7 @@ mod linked { statements: Vec>, messages: Vec, witness: Vec>, + custom_bounds: Vec<((usize, usize), Bounds)>, // linked proof fields compiled_zkp_program: Option<&'z CompiledZkpProgram>, @@ -405,14 +411,51 @@ mod linked { constant_inputs: Vec, } + /// A builder for an [`Sdlp`] (without any linked ZKP program). + pub type SdlpBuilder<'r, 'k> = LogProofBuilder<'r, 'k, 'static, Fhe, ()>; + + impl<'r, 'k> SdlpBuilder<'r, 'k> { + /// Create a new [`SdlpBuilder`]. + pub fn new(runtime: &'r FheRuntime) -> Self { + LogProofBuilder::new_internal(runtime) + } + + /// Build the [`Sdlp`]. + pub fn build(self) -> Result { + self.build_logproof() + } + } + + /// A builder for a [`LinkedProof`]. + pub type LinkedProofBuilder<'r, 'k, 'z> = + LogProofBuilder<'r, 'k, 'z, FheZkp, BulletproofsBackend>; + + impl<'r, 'k, 'z> LinkedProofBuilder<'r, 'k, 'z> { + /// Create a new [`LinkedProofBuilder`]. + pub fn new(runtime: &'r FheZkpRuntime) -> Self { + LogProofBuilder::new_internal(runtime) + } + + /// Build just the [`Sdlp`] portion of the linked proof. + pub fn build_sdlp(&self) -> Result { + self.build_logproof() + } + + /// Build the [`LinkedProof`]. + pub fn build(&mut self) -> Result { + self.build_linkedproof() + } + } + impl<'r, 'k, 'z, M: marker::Fhe, Z> LogProofBuilder<'r, 'k, 'z, M, Z> { /// Create a new [`LogProofBuilder`]. - pub fn new(runtime: &'r GenericRuntime) -> Self { + fn new_internal(runtime: &'r GenericRuntime) -> Self { Self { runtime, statements: vec![], messages: vec![], witness: vec![], + custom_bounds: vec![], compiled_zkp_program: None, linked_inputs: vec![], private_inputs: vec![], @@ -450,7 +493,7 @@ mod linked { } /// Encrypt a plaintext, adding the encryption statement to the logproof and returning the - /// message to optionally be [encrypted again](`Self::encrypt_msg`), that is, _shared_ + /// message to optionally be [encrypted again](`Self::reencrypt`), that is, _shared_ /// with another logproof statement. /// /// If you do not want to add the encryption statement to the proof, just use [the @@ -467,7 +510,7 @@ mod linked { } /// Encrypt a plaintext, adding the encryption statement to the logproof and returning the - /// message to optionally be [encrypted again](`Self::encrypt_msg`), that is, _shared_ + /// message to optionally be [encrypted again](`Self::reencrypt_symmetric`), that is, _shared_ /// with another logproof statement. /// /// If you do not want to add the encryption statement to the proof, just use [the @@ -516,13 +559,13 @@ mod linked { Ok((ct, Message(msg_internal))) } - /// Encrypt an existing message, adding the new encryption statement to the logproof. + /// Re-encrypt an existing message, adding the new encryption statement to the logproof. /// - /// This method purposefully reveals that two ciphertexts encrypt the same underlying value. If - /// this is not what you want, use [`Self::encrypt`]. + /// This method purposefully reveals that two ciphertexts encrypt the same underlying + /// plaintext. 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_msg( + pub fn reencrypt( &mut self, message: &E, public_key: &'k PublicKey, @@ -536,14 +579,14 @@ mod linked { ) } - /// Encrypt an existing message symmetrically, adding the new encryption statement to the + /// Re-encrypt an existing message symmetrically, adding the new encryption statement to the /// logproof. /// - /// This method purposefully reveals that two ciphertexts encrypt the same underlying value. If - /// this is not what you want, use [`Self::encrypt_symmetric`]. + /// This method purposefully reveals that two ciphertexts encrypt the same underlying + /// plaintext. If this is not what you want, use [`Self::encrypt_symmetric`]. /// /// This method assumes that you've created the `message` argument with _this_ builder. - pub fn encrypt_symmetric_msg( + pub fn reencrypt_symmetric( &mut self, message: &E, private_key: &'k PrivateKey, @@ -561,8 +604,14 @@ mod linked { /// message to be shared with another proof statement. /// /// Use this method if you have an existing ciphertext and want to prove that it is well - /// formed, and you intend to re-encrypt it, refreshing the noise but not the plaintext - /// polynomial encoding. + /// formed, and you intend to [reencrypt](Self::reencrypt) it, refreshing the noise but _not_ + /// the plaintext polynomial encoding. + /// + /// # Remarks + /// This method is only useful in niche scenarios; you probably want to [return a + /// link](Self::decrypt_returning_link) instead, encrypt a new ciphertext with [fresh + /// noise and a fresh encoding](Self::encrypt_returning_link), and prove equality within a + /// linked ZKP program. pub fn decrypt_returning_msg

( &mut self, ciphertext: &Ciphertext, @@ -698,32 +747,41 @@ mod linked { }) } - /// Build the [`Sdlp`] for the statements added to this builder. + /// Customize bounds for a given entry in the secret `S`. Note the verifier must also + /// provide the same custom bounds when building the verifier knowledge. /// - /// 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 { + /// # Remarks + /// This is for advanced users that are familiar with the shape of the matrices documented + /// in [`logproof::bfv_statement::generate_prover_knowledge`]. + pub fn add_custom_bounds(&mut self, row: usize, col: usize, bounds: Bounds) -> &mut Self { + self.custom_bounds.push(((row, col), bounds)); + self + } + + /// Build the [`Sdlp`] for the statements added to this builder. + fn build_logproof(&self) -> Result { Sdlp::create(&self.build_sdlp_pk()?) } - fn build_sdlp_pk(&self) -> Result { + fn build_sdlp_pk(&self) -> Result { let params = self.runtime.params(); - match ¶ms.coeff_modulus[..] { - SealQ128_1024::Q => Ok(SealSdlpProverKnowledge::from( - self.build_sdlp_pk_generic::<1, SealQ128_1024>()?, - )), - SealQ128_2048::Q => Ok(SealSdlpProverKnowledge::from( - self.build_sdlp_pk_generic::<1, SealQ128_2048>()?, - )), - SealQ128_4096::Q => Ok(SealSdlpProverKnowledge::from( - self.build_sdlp_pk_generic::<2, SealQ128_4096>()?, - )), - SealQ128_8192::Q => Ok(SealSdlpProverKnowledge::from( - self.build_sdlp_pk_generic::<3, SealQ128_8192>()?, - )), - _ => Err(BuilderError::UnsupportedParameters(Box::new(params.clone())).into()), + let mut pk: SdlpProverKnowledge = match ¶ms.coeff_modulus[..] { + SealQ128_1024::Q => Ok(self.build_sdlp_pk_generic::<1, SealQ128_1024>()?.into()), + SealQ128_2048::Q => Ok(self.build_sdlp_pk_generic::<1, SealQ128_2048>()?.into()), + SealQ128_4096::Q => Ok(self.build_sdlp_pk_generic::<2, SealQ128_4096>()?.into()), + SealQ128_8192::Q => Ok(self.build_sdlp_pk_generic::<3, SealQ128_8192>()?.into()), + _ => Err(BuilderError::UnsupportedParameters(Box::new( + params.clone(), + ))), + }?; + + // Add the custom bounds, if any + let bounds = pk.bounds_mut(); + for ((row, col), bound) in &self.custom_bounds { + bounds[(*row, *col)] = bound.clone(); } + + Ok(pk) } fn build_sdlp_pk_generic>( @@ -739,20 +797,13 @@ mod linked { ctx, )) } - - fn mk_bounds(&self) -> Bounds { - let params = self.runtime.params(); - let mut bounds = vec![params.plain_modulus.ceil_log2(); 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> { /// Encrypt a plaintext intended for linking. /// /// The returned `LinkedMessage` can be used: - /// 1. to add an encryption statement of ciphertext equality to the logproof (see [`Self::encrypt_msg`]). + /// 1. to add an encryption statement of ciphertext equality to the logproof (see [`Self::reencrypt`]). /// 2. as a linked input to a ZKP program (see [`Self::linked_input`]). pub fn encrypt_returning_link

( &mut self, @@ -763,7 +814,7 @@ mod linked { P: LinkWithZkp + TryIntoPlaintext + TypeName, { // The user intends to link this message, so add a more conservative bound - let bounds = self.mk_bounds::

(); + let bounds = mk_bounds::

(self.runtime.params()); let (ct, msg) = self.encrypt_returning_msg_internal( message, Key::Public(public_key), @@ -776,7 +827,7 @@ mod linked { /// Symmetrically encrypt a plaintext intended for linking. /// /// The returned `LinkedMessage` can be used: - /// 1. to add an encryption statement of ciphertext equality to the logproof (see [`Self::encrypt_msg`]). + /// 1. to add an encryption statement of ciphertext equality to the logproof (see [`Self::reencrypt_symmetric`]). /// 2. as a linked input to a ZKP program (see [`Self::linked_input`]). pub fn encrypt_symmetric_returning_link

( &mut self, @@ -787,7 +838,7 @@ mod linked { P: LinkWithZkp + TryIntoPlaintext + TypeName, { // The user intends to link this message, so add a more conservative bound - let bounds = self.mk_bounds::

(); + let bounds = mk_bounds::

(self.runtime.params()); let (ct, msg) = self.encrypt_returning_msg_internal( message, Key::Private(private_key), @@ -798,7 +849,13 @@ mod linked { } /// Decrypt a ciphertext, adding the decryption statement to the logproof and returning the - /// linked message to be shared with another logproof statement or linked to a ZKP program. + /// linked message to be [linked to a ZKP program](Self::linked_input). + /// + /// # Remarks + /// You can [reencrypt](Self::reencrypt) the returned linked message, however while this + /// will reset the noise level it will not refresh the plaintext polynomial encoding. Most likely, + /// you want to use [`Self::encrypt_returning_link`] to encrypt a _new_ plaintext encoding + /// instead, and prove equality within a linked ZKP program. pub fn decrypt_returning_link

( &mut self, ciphertext: &Ciphertext, @@ -807,7 +864,7 @@ mod linked { where P: LinkWithZkp + TryIntoPlaintext + TryFromPlaintext + TypeName, { - let bounds = self.mk_bounds::

(); + let bounds = mk_bounds::

(self.runtime.params()); let (pt, msg) = self.decrypt_internal::

(ciphertext, private_key, Some(bounds))?; let zkp_type = P::ZkpType::::type_name(); Ok((pt, LinkedMessage::from_message(msg, zkp_type))) @@ -815,7 +872,7 @@ mod linked { /// Add a ZKP program to be linked with the logproof. /// - /// This method is required to call [`Self::build_linkedproof`]. + /// This method is required to call [`LinkedProofBuilder::build`]. 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( @@ -859,7 +916,7 @@ mod linked { /// Output a [`LinkedProof`] from the encryption statements and ZKP program and inputs added to /// this builder. - pub fn build_linkedproof(&mut self) -> Result { + fn build_linkedproof(&self) -> Result { 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") @@ -887,6 +944,12 @@ mod linked { } } + fn mk_bounds(params: &Params) -> Bounds { + let mut bounds = vec![params.plain_modulus.ceil_log2(); P::DEGREE_BOUND]; + bounds.resize(params.lattice_dimension as usize, 0); + Bounds(bounds) + } + impl StatementParams for Params { fn degree(&self) -> u64 { self.lattice_dimension @@ -900,4 +963,381 @@ mod linked { self.coeff_modulus.clone() } } + + /// This message type is used as an analog to the `Message` and `LinkedMessage` of the proof + /// builder. While the proof builder actually takes in the private messages, the verifier simply + /// deals with "references" to messages, i.e. their indicies and ordering. + #[derive(Clone, Debug)] + pub struct MessageRef { + id: usize, + len: usize, + } + + /// A builder for verifying an [`Sdlp`] or [`LinkedProof`]. + /// + /// The idea is that you call the same methods, in the same order, on the verification builder + /// as you do on the proof builder. Library or protocol authors can add wrappers around this, + /// via labels or other semantic attachments, but at this level, all that matters is the + /// ordering. + pub struct LogProofVerificationBuilder<'r, 'k, 'z, M, B> { + runtime: &'r GenericRuntime, + + // log proof fields + statements: Vec>, + message_bounds: Vec>, + sdlp: Option, + custom_bounds: Vec<((usize, usize), Bounds)>, + + // linked proof fields + compiled_zkp_program: Option<&'z CompiledZkpProgram>, + public_inputs: Vec, + constant_inputs: Vec, + linkedproof: Option, + } + + /// A builder for verifying an [`Sdlp`]. + pub type SdlpVerificationBuilder<'r, 'k> = + LogProofVerificationBuilder<'r, 'k, 'static, Fhe, ()>; + + impl<'r, 'k> SdlpVerificationBuilder<'r, 'k> { + /// Create a new [`SdlpVerificationBuilder`]. + pub fn new(runtime: &'r FheRuntime) -> Self { + LogProofVerificationBuilder::new_internal(runtime) + } + + /// Set the SDLP to prove. + pub fn proof(&mut self, proof: Sdlp) -> &mut Self { + self.sdlp = Some(proof); + self + } + + /// Verify the SDLP. + pub fn verify(&mut self) -> Result<()> { + let vk = self.build_sdlp_vk()?; + let sdlp = self.sdlp.as_mut().ok_or_else(|| { + BuilderError::user_error( + "You must supply a proof to the verification builder before calling `verify`. Use the `.proof()` method.", + ) + })?; + sdlp.verify(&vk) + } + } + + /// A builder for verifying a [`LinkedProof`]. + pub type LinkedProofVerificationBuilder<'r, 'k, 'z> = + LogProofVerificationBuilder<'r, 'k, 'z, FheZkp, BulletproofsBackend>; + + impl<'r, 'k, 'z> LinkedProofVerificationBuilder<'r, 'k, 'z> { + /// Create a new [`LinkedProofVerificationBuilder`]. + pub fn new(runtime: &'r FheZkpRuntime) -> Self { + LogProofVerificationBuilder::new_internal(runtime) + } + + /// Set the linked proof to prove. + pub fn proof(&mut self, proof: LinkedProof) -> &mut Self { + self.linkedproof = Some(proof); + self + } + + /// Verify the linked proof. + pub fn verify(&mut self) -> Result<()> { + let vk = self.build_sdlp_vk()?; + let linkedproof = self.linkedproof.as_mut().ok_or_else(|| { + BuilderError::user_error( + "You must supply a proof to the verification builder before calling `verify`. Use the `.proof()` method.", + ) + })?; + 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") + })?; + linkedproof.verify( + &vk, + program, + self.public_inputs.drain(0..).collect(), + self.constant_inputs.drain(0..).collect(), + ) + } + } + + impl<'r, 'k, 'z, M: marker::Fhe, Z> LogProofVerificationBuilder<'r, 'k, 'z, M, Z> { + fn new_internal(runtime: &'r GenericRuntime) -> Self { + Self { + runtime, + statements: vec![], + message_bounds: vec![], + custom_bounds: vec![], + compiled_zkp_program: None, + public_inputs: vec![], + constant_inputs: vec![], + sdlp: None, + linkedproof: None, + } + } + + /// Add verifier knowledge for [`LogProofBuilder::decrypt_returning_msg`]. + pub fn decrypt_returning_msg(&mut self, ciphertext: &Ciphertext) -> Result { + self.decrypt_internal(ciphertext, None) + } + + fn decrypt_internal( + &mut self, + ciphertext: &Ciphertext, + bounds: Option, + ) -> Result { + let start_idx = self.message_bounds.len(); + for ct in ciphertext.inner_as_seal_ciphertext()? { + self.statements.push(BfvProofStatement::Decryption { + message_id: self.message_bounds.len(), + ciphertext: ct.data.clone(), + }); + self.message_bounds.push(bounds.clone()); + } + let end_idx = self.message_bounds.len(); + + Ok(MessageRef { + id: start_idx, + len: end_idx - start_idx, + }) + } + + /// Add verifier knowledge for [`LogProofBuilder::encrypt`]. + pub fn encrypt( + &mut self, + ciphertext: &Ciphertext, + public_key: &'k PublicKey, + ) -> Result<()> { + self.encrypt_internal(ciphertext, public_key, None)?; + Ok(()) + } + + /// Add verifier knowledge for [`LogProofBuilder::encrypt_returning_msg`]. + pub fn encrypt_returning_msg( + &mut self, + ciphertext: &Ciphertext, + public_key: &'k PublicKey, + ) -> Result { + self.encrypt_internal(ciphertext, public_key, None) + } + + fn encrypt_internal( + &mut self, + ciphertext: &Ciphertext, + public_key: &'k PublicKey, + bounds: Option, + ) -> Result { + let start_idx = self.message_bounds.len(); + for ct in ciphertext.inner_as_seal_ciphertext()? { + self.statements + .push(BfvProofStatement::PublicKeyEncryption { + message_id: self.message_bounds.len(), + ciphertext: ct.data.clone(), + public_key: Cow::Borrowed(&public_key.public_key.data), + }); + self.message_bounds.push(bounds.clone()); + } + let end_idx = self.message_bounds.len(); + + Ok(MessageRef { + id: start_idx, + len: end_idx - start_idx, + }) + } + + /// Add verifier knowledge for [`LogProofBuilder::encrypt_symmetric`]. + pub fn encrypt_symmetric(&mut self, ciphertext: &Ciphertext) -> Result<()> { + self.encrypt_symmetric_internal(ciphertext, None)?; + Ok(()) + } + + /// Add verifier knowledge for [`LogProofBuilder::encrypt_symmetric_returning_msg`]. + pub fn encrypt_symmetric_returning_msg( + &mut self, + ciphertext: &Ciphertext, + ) -> Result { + self.encrypt_symmetric_internal(ciphertext, None) + } + + fn encrypt_symmetric_internal( + &mut self, + ciphertext: &Ciphertext, + bounds: Option, + ) -> Result { + let start_idx = self.message_bounds.len(); + for ct in ciphertext.inner_as_seal_ciphertext()? { + self.statements + .push(BfvProofStatement::PrivateKeyEncryption { + message_id: self.message_bounds.len(), + ciphertext: ct.data.clone(), + }); + self.message_bounds.push(bounds.clone()); + } + let end_idx = self.message_bounds.len(); + + Ok(MessageRef { + id: start_idx, + len: end_idx - start_idx, + }) + } + + /// Add verifier knowledge for [`LogProofBuilder::reencrypt`]. + pub fn reencrypt( + &mut self, + message: &MessageRef, + ciphertext: &Ciphertext, + public_key: &'k PublicKey, + ) -> Result<()> { + let mut ct_size = 0; + for (i, ct) in ciphertext.inner_as_seal_ciphertext()?.iter().enumerate() { + self.statements + .push(BfvProofStatement::PublicKeyEncryption { + message_id: message.id + i, + ciphertext: ct.data.clone(), + public_key: Cow::Borrowed(&public_key.public_key.data), + }); + ct_size += 1; + } + if ct_size != message.len { + return Err(BuilderError::user_error( + "The ciphertext's length does not match the existing message ref. This is likely a type mismatch.", + )); + } + Ok(()) + } + + /// Add verifier knowledge for [`LogProofBuilder::reencrypt_symmetric`]. + pub fn reencrypt_symmetric( + &mut self, + message: &MessageRef, + ciphertext: &Ciphertext, + ) -> Result<()> { + let mut ct_size = 0; + for (i, ct) in ciphertext.inner_as_seal_ciphertext()?.iter().enumerate() { + self.statements + .push(BfvProofStatement::PrivateKeyEncryption { + message_id: message.id + i, + ciphertext: ct.data.clone(), + }); + ct_size += 1; + } + if ct_size != message.len { + return Err(BuilderError::user_error( + "The ciphertext's length does not match the existing message ref. This is likely a type mismatch.", + )); + } + Ok(()) + } + + /// Customize bounds for a given entry in the secret `S`. Note these custom bounds must + /// match those provided during the proof generation. + /// + /// # Remarks + /// This is for advanced users that are familiar with the shape of the matrices documented + /// in [`logproof::bfv_statement::generate_prover_knowledge`]. + pub fn add_custom_bounds(&mut self, row: usize, col: usize, bounds: Bounds) -> &mut Self { + self.custom_bounds.push(((row, col), bounds)); + self + } + + /// Build the [`SdlpVerifierKnowledge`] for the statements added to this builder. + pub(crate) fn build_sdlp_vk(&self) -> Result { + let params = self.runtime.params(); + let mut vk: SdlpVerifierKnowledge = match ¶ms.coeff_modulus[..] { + SealQ128_1024::Q => Ok(self.build_sdlp_vk_generic::<1, SealQ128_1024>()?.into()), + SealQ128_2048::Q => Ok(self.build_sdlp_vk_generic::<1, SealQ128_2048>()?.into()), + SealQ128_4096::Q => Ok(self.build_sdlp_vk_generic::<2, SealQ128_4096>()?.into()), + SealQ128_8192::Q => Ok(self.build_sdlp_vk_generic::<3, SealQ128_8192>()?.into()), + _ => Err(BuilderError::UnsupportedParameters(Box::new( + params.clone(), + ))), + }?; + + // Add the custom bounds, if any + let bounds = vk.bounds_mut(); + for ((row, col), bound) in &self.custom_bounds { + bounds[(*row, *col)] = bound.clone(); + } + + Ok(vk) + } + + fn build_sdlp_vk_generic>( + &self, + ) -> Result>>> { + Ok(bfv_statement::generate_verifier_knowledge( + &self.statements, + &self.message_bounds, + self.runtime.params(), + self.runtime.context(), + )) + } + } + + impl<'r, 'k, 'z, M: marker::Fhe + marker::Zkp> + LogProofVerificationBuilder<'r, 'k, 'z, M, BulletproofsBackend> + { + /// Add verifier knowledge for [`LogProofBuilder::encrypt_returning_link`]. + pub fn encrypt_returning_link

( + &mut self, + ciphertext: &Ciphertext, + public_key: &'k PublicKey, + ) -> Result + where + P: LinkWithZkp, + { + self.encrypt_internal( + ciphertext, + public_key, + Some(mk_bounds::

(self.runtime.params())), + ) + } + + /// Add verifier knowledge for [`LogProofBuilder::encrypt_symmetric_returning_link`]. + pub fn encrypt_symmetric_returning_link

( + &mut self, + ciphertext: &Ciphertext, + ) -> Result + where + P: LinkWithZkp, + { + self.encrypt_symmetric_internal(ciphertext, Some(mk_bounds::

(self.runtime.params()))) + } + + /// Add verifier knowledge for [`LogProofBuilder::decrypt_returning_link`]. + pub fn decrypt_returning_link

(&mut self, ciphertext: &Ciphertext) -> Result + where + P: LinkWithZkp, + { + self.decrypt_internal(ciphertext, Some(mk_bounds::

(self.runtime.params()))) + } + + /// Add the ZKP program to verify. + /// + /// This method is required to call [`Self::verify`]. + 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 public input to the ZKP program. + pub fn public_input(&mut self, input: impl Into) -> &mut Self { + self.public_inputs.push(input.into()); + self + } + + /// Add a constant input to the ZKP program. + pub fn constant_input(&mut self, input: impl Into) -> &mut Self { + self.constant_inputs.push(input.into()); + self + } + } } diff --git a/sunscreen_runtime/src/linked.rs b/sunscreen_runtime/src/linked.rs index 3da8315e6..62ebf3626 100644 --- a/sunscreen_runtime/src/linked.rs +++ b/sunscreen_runtime/src/linked.rs @@ -7,14 +7,16 @@ use bulletproofs::{BulletproofGens, GeneratorsChain, PedersenGens}; use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar}; use log::trace; use logproof::{ + linear_algebra::Matrix, math::rand256, rings::{ZqSeal128_1024, ZqSeal128_2048, ZqSeal128_4096, ZqSeal128_8192}, - InnerProductVerifierKnowledge, LogProof, LogProofGenerators, LogProofProverKnowledge, + Bounds, InnerProductVerifierKnowledge, LogProof, LogProofGenerators, LogProofProverKnowledge, LogProofVerifierKnowledge, ProofError, }; use merlin::Transcript; use paste::paste; use seq_macro::seq; +use serde::{Deserialize, Serialize}; use sunscreen_compiler_common::Type; use sunscreen_zkp_backend::{ bulletproofs::{ @@ -25,24 +27,23 @@ use sunscreen_zkp_backend::{ use crate::{CompiledZkpProgram, Result, TypeNameInstance, ZkpProgramInput, ZkpRuntime}; -#[derive(Debug, Clone)] -/// SDLP proof and associated information for verification +#[derive(Serialize, Deserialize, Clone)] +/// SDLP proof pub struct Sdlp { proof: LogProof, - vk: SealSdlpVerifierKnowledge, g: Vec, h: Vec, u: RistrettoPoint, } -#[derive(Clone)] +#[derive(Serialize, Deserialize, Clone)] /// R1CS BP proof and associated information for verification struct BP { proof: Proof, verifier_parameters: BulletproofVerifierParameters, } -#[derive(Clone)] +#[derive(Serialize, Deserialize, Clone)] /// A linked proof between an SDLP and R1CS BP pub struct LinkedProof { sdlp: Sdlp, @@ -96,7 +97,7 @@ 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 + /// Note that the [builder methods](`crate::LinkedProofBuilder`) offer an easier way to construct this /// proof. See the user documentation for more information. /// /// Arguments: @@ -109,8 +110,8 @@ impl LinkedProof { /// * `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( - prover_knowledge: &SealSdlpProverKnowledge, + pub(crate) fn create( + prover_knowledge: &SdlpProverKnowledge, shared_indices: &[(usize, usize)], shared_types: &[Type], program: &CompiledZkpProgram, @@ -159,7 +160,6 @@ impl LinkedProof { let sdlp_package = Sdlp { proof: sdlp_proof, - vk, g: gens.g, h: gens.h, u, @@ -245,8 +245,9 @@ impl LinkedProof { /// * `public_inputs`: The public inputs to the ZKP program /// * `constant_inputs`: The constant inputs to the ZKP program /// - pub fn verify( + pub(crate) fn verify( &self, + sdlp_vk: &SdlpVerifierKnowledge, program: &CompiledZkpProgram, public_inputs: Vec, constant_inputs: Vec, @@ -258,8 +259,7 @@ impl LinkedProof { let mut transcript = Transcript::new(Self::TRANSCRIPT_LABEL); - self.sdlp - .vk + sdlp_vk .verify( &self.sdlp.proof, &mut transcript, @@ -297,17 +297,15 @@ impl Sdlp { /// 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 { + /// The [builder methods](`crate::SdlpBuilder`) offer an easier way to construct this proof. + pub(crate) fn create(prover_knowledge: &SdlpProverKnowledge) -> Result { let mut transcript = Transcript::new(Self::TRANSCRIPT_LABEL); - let vk = prover_knowledge.vk(); - let gen = LogProofGenerators::new(vk.l() as usize); + let gen = LogProofGenerators::new(prover_knowledge.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, @@ -315,32 +313,31 @@ impl Sdlp { } /// This function verifies a solo SDLP. - pub fn verify(&self) -> Result<()> { + pub(crate) fn verify(&self, vk: &SdlpVerifierKnowledge) -> Result<()> { let mut transcript = Transcript::new(Self::TRANSCRIPT_LABEL); - self.vk - .verify(&self.proof, &mut transcript, &self.g, &self.h, &self.u)?; + 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); +pub(crate) struct SdlpProverKnowledge(pub(crate) SdlpProverKnowledgeInternal); /// The verifier knowledge of an [`Sdlp`]. -#[derive(Debug, Clone)] -pub struct SealSdlpVerifierKnowledge(pub(crate) SealSdlpVerifierKnowledgeInternal); +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct SdlpVerifierKnowledge(pub(crate) SdlpVerifierKnowledgeInternal); -pub(crate) enum SealSdlpProverKnowledgeInternal { +pub(crate) enum SdlpProverKnowledgeInternal { LP1(LogProofProverKnowledge), LP2(LogProofProverKnowledge), LP3(LogProofProverKnowledge), LP4(LogProofProverKnowledge), } -#[derive(Debug, Clone)] -pub(crate) enum SealSdlpVerifierKnowledgeInternal { +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) enum SdlpVerifierKnowledgeInternal { LP1(LogProofVerifierKnowledge), LP2(LogProofVerifierKnowledge), LP3(LogProofVerifierKnowledge), @@ -350,14 +347,14 @@ pub(crate) enum SealSdlpVerifierKnowledgeInternal { macro_rules! impl_from { ($zq_type:ty, $variant:ident) => { paste! { - impl From> for SealSdlpProverKnowledge { + impl From> for SdlpProverKnowledge { fn from(k: LogProofProverKnowledge<$zq_type>) -> Self { - Self(SealSdlpProverKnowledgeInternal::$variant(k)) + Self(SdlpProverKnowledgeInternal::$variant(k)) } } - impl From> for SealSdlpVerifierKnowledge { + impl From> for SdlpVerifierKnowledge { fn from(k: LogProofVerifierKnowledge<$zq_type>) -> Self { - Self(SealSdlpVerifierKnowledgeInternal::$variant(k)) + Self(SdlpVerifierKnowledgeInternal::$variant(k)) } } } @@ -377,15 +374,15 @@ macro_rules! seq_zq { ) } -impl SealSdlpProverKnowledge { +impl SdlpProverKnowledge { /// 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 { + pub(crate) 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), + SdlpProverKnowledgeInternal::LP~N(pk) => pk.s_binary_by_index(index), )* } }) @@ -394,18 +391,29 @@ impl SealSdlpProverKnowledge { /// Get the verifier knowledge component. /// /// Delegation to [`LogProofProverKnowledge::vk`]. - pub fn vk(&self) -> SealSdlpVerifierKnowledge { + pub(crate) fn vk(&self) -> SdlpVerifierKnowledge { seq_zq!({ match &self.0 { #( - SealSdlpProverKnowledgeInternal::LP~N(pk) => { - SealSdlpVerifierKnowledge(SealSdlpVerifierKnowledgeInternal::LP~N(pk.vk.clone())) + SdlpProverKnowledgeInternal::LP~N(pk) => { + SdlpVerifierKnowledge(SdlpVerifierKnowledgeInternal::LP~N(pk.vk.clone())) } )* } }) } + /// Get a mutable reference to the bounds on the secret `S`. + pub(crate) fn bounds_mut(&mut self) -> &mut Matrix { + seq_zq!({ + match &mut self.0 { + #( + SdlpProverKnowledgeInternal::LP~N(pk) => &mut pk.vk.bounds, + )* + } + }) + } + /// Create a shared `LogProof`. /// /// Delegation to [`LogProof::create_with_shared`]. @@ -421,7 +429,7 @@ impl SealSdlpProverKnowledge { seq_zq!({ match &self.0 { #( - SealSdlpProverKnowledgeInternal::LP~N(pk) => { + SdlpProverKnowledgeInternal::LP~N(pk) => { LogProof::create_with_shared(transcript, pk, g, h, u, half_rho, shared_indices) } )* @@ -442,14 +450,14 @@ impl SealSdlpProverKnowledge { seq_zq!({ match &self.0 { #( - SealSdlpProverKnowledgeInternal::LP~N(pk) => LogProof::create(transcript, pk, g, h, u), + SdlpProverKnowledgeInternal::LP~N(pk) => LogProof::create(transcript, pk, g, h, u), )* } }) } } -impl SealSdlpVerifierKnowledge { +impl SdlpVerifierKnowledge { /// Get the length in bits of the binary expansion of the serialized secret * vectors. /// /// Delegate to [`LogProofVerifierKnowledge::l`]. @@ -457,20 +465,31 @@ impl SealSdlpVerifierKnowledge { seq_zq!({ match &self.0 { #( - SealSdlpVerifierKnowledgeInternal::LP~N(vk) => vk.l(), + SdlpVerifierKnowledgeInternal::LP~N(vk) => vk.l(), )* } }) } - /// Get the ranges in the serialized coefficients of `S` corresponding to the bounds + /// Get the ranges in the serialized coefficients of `S` corresponding to the bounds. /// /// Delegate to [`LogProofVerifierKnowledge::b_slices`]. pub fn b_slices(&self) -> Vec>> { seq_zq!({ match &self.0 { #( - SealSdlpVerifierKnowledgeInternal::LP~N(vk) => vk.b_slices(), + SdlpVerifierKnowledgeInternal::LP~N(vk) => vk.b_slices(), + )* + } + }) + } + + /// Get a mutable reference to the bounds on the secret `S`. + pub fn bounds_mut(&mut self) -> &mut Matrix { + seq_zq!({ + match &mut self.0 { + #( + SdlpVerifierKnowledgeInternal::LP~N(vk) => &mut vk.bounds, )* } }) @@ -490,7 +509,7 @@ impl SealSdlpVerifierKnowledge { seq_zq!({ match &self.0 { #( - SealSdlpVerifierKnowledgeInternal::LP~N(vk) => logproof.verify(transcript, vk, g, h, u), + SdlpVerifierKnowledgeInternal::LP~N(vk) => logproof.verify(transcript, vk, g, h, u), )* } }) diff --git a/sunscreen_runtime/src/runtime.rs b/sunscreen_runtime/src/runtime.rs index aceed4936..6fe1e13b7 100644 --- a/sunscreen_runtime/src/runtime.rs +++ b/sunscreen_runtime/src/runtime.rs @@ -933,6 +933,21 @@ impl FheZkpRuntime { } } +#[cfg(feature = "linkedproofs")] +impl FheZkpRuntime { + /// Create a new [`LinkedProofBuilder`](crate::LinkedProofBuilder). + pub fn linkedproof_builder<'k, 'z>(&self) -> crate::LinkedProofBuilder<'_, 'k, 'z> { + crate::LinkedProofBuilder::new(self) + } + + /// Create a new [`LinkedProofVerificationBuilder`](crate::LinkedProofVerificationBuilder). + pub fn linkedproof_verification_builder<'k, 'z>( + &self, + ) -> crate::LinkedProofVerificationBuilder<'_, 'k, 'z> { + crate::LinkedProofVerificationBuilder::new(self) + } +} + /** * A runtime capable of only FHE operations. */ @@ -947,6 +962,19 @@ impl FheRuntime { } } +#[cfg(feature = "linkedproofs")] +impl FheRuntime { + /// Create a new [`SdlpBuilder`](crate::SdlpBuilder). + pub fn sdlp_builder<'k>(&self) -> crate::SdlpBuilder<'_, 'k> { + crate::SdlpBuilder::new(self) + } + + /// Create a new [`SdlpVerificationBuilder`](crate::SdlpVerificationBuilder). + pub fn sdlp_verification_builder<'k>(&self) -> crate::SdlpVerificationBuilder<'_, 'k> { + crate::SdlpVerificationBuilder::new(self) + } +} + /** * A runtime capable of only ZKP operations. */ diff --git a/sunscreen_zkp_backend/src/bulletproofs.rs b/sunscreen_zkp_backend/src/bulletproofs.rs index c2f6f981e..c2c35f5ce 100644 --- a/sunscreen_zkp_backend/src/bulletproofs.rs +++ b/sunscreen_zkp_backend/src/bulletproofs.rs @@ -440,7 +440,7 @@ fn constraint_count(graph: &ExecutableZkpProgram) -> Result { } /// Parameters for verifying Bulletproof circuit. -#[derive(Clone)] +#[derive(Serialize, Deserialize, Clone)] pub struct BulletproofVerifierParameters { pedersen_generators: PedersenGens, bulletproof_generators: BulletproofGens,