Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions willow/src/shell/vahe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,44 @@ impl EncryptVerify for ShellVahe {
}
Ok(())
}

fn verify_multiple_encrypts(
&self,
items: &[(&Self::EncryptionProof, &Self::PartialDecCiphertext, &[u8])],
) -> Status {
if items.is_empty() {
return Ok(());
}

let (transcript, proof_seed) = self.get_transcript_and_proof_seed(b"encryption")?;
let verifier = RlweRelationVerifier::new(proof_seed.as_bytes(), self.ahe.num_coeffs());

for &(proof, ciphertext, nonce) in items {
let num_polynomials = ciphertext.0.len();
if proof.0.len() != num_polynomials {
return Err(status::permission_denied(
"Invalid proof. Proof length does not match number of polynomials in ciphertext.",
));
}

let mut transcript = transcript.clone();
transcript.append_message(b"nonce:", nonce);
for i in 0..num_polynomials {
let statement = RlweRelationProofStatement {
n: self.ahe.num_coeffs(),
context: self.ahe.rns_context(),
a: &self.ahe.public_key_component_a()?,
flip_a: false,
c: &ciphertext.0[i],
q: self.q,
bound_r: 1,
bound_e: 16,
};
verifier.verify(&statement, &proof.0[i], &mut transcript)?;
}
}
Ok(())
}
}

impl VerifiablePartialDec for ShellVahe {
Expand Down Expand Up @@ -627,6 +665,77 @@ mod test {
Ok(())
}

#[gtest]
fn test_verify_multiple_encrypts() -> googletest::Result<()> {
let vahe = ShellVahe::new(make_ahe_config(), CONTEXT_STRING)?;
let seed = SingleThreadHkdfPrng::generate_seed()?;
let mut prng = SingleThreadHkdfPrng::create(&seed)?;
let (_, pk_share, _) = vahe.verifiable_key_gen(&mut prng)?;
let pk = vahe.aggregate_public_key_shares(&[pk_share])?;
let plaintext = vec![47i64; 8];
let nonce1 = b"nonce1";
let nonce2 = b"nonce2";

let (ciphertext1, proof1) = vahe.verifiable_encrypt(&plaintext, &pk, nonce1, &mut prng)?;
let (ciphertext2, proof2) = vahe.verifiable_encrypt(&plaintext, &pk, nonce2, &mut prng)?;

let items = vec![
(&proof1, &ciphertext1.component_a, nonce1 as &[u8]),
(&proof2, &ciphertext2.component_a, nonce2 as &[u8]),
];

vahe.verify_multiple_encrypts(&items)?;
Ok(())
}

#[gtest]
fn test_verify_multiple_encrypts_with_bad_nonce() -> googletest::Result<()> {
let vahe = ShellVahe::new(make_ahe_config(), CONTEXT_STRING)?;
let seed = SingleThreadHkdfPrng::generate_seed()?;
let mut prng = SingleThreadHkdfPrng::create(&seed)?;
let (_, pk_share, _) = vahe.verifiable_key_gen(&mut prng)?;
let pk = vahe.aggregate_public_key_shares(&[pk_share])?;
let plaintext = vec![47i64; 8];
let nonce1 = b"nonce1";
let nonce2 = b"nonce2";

let (ciphertext1, proof1) = vahe.verifiable_encrypt(&plaintext, &pk, nonce1, &mut prng)?;
let (ciphertext2, proof2) = vahe.verifiable_encrypt(&plaintext, &pk, nonce2, &mut prng)?;

let items = vec![
(&proof1, &ciphertext1.component_a, nonce1 as &[u8]),
(&proof2, &ciphertext2.component_a, b"bad_nonce" as &[u8]),
];

let status = vahe.verify_multiple_encrypts(&items);
assert!(status.is_err());
Ok(())
}

#[gtest]
fn test_verify_multiple_encrypts_with_bad_proof() -> googletest::Result<()> {
let vahe = ShellVahe::new(make_ahe_config(), CONTEXT_STRING)?;
let seed = SingleThreadHkdfPrng::generate_seed()?;
let mut prng = SingleThreadHkdfPrng::create(&seed)?;
let (_, pk_share, _) = vahe.verifiable_key_gen(&mut prng)?;
let pk = vahe.aggregate_public_key_shares(&[pk_share])?;
let plaintext = vec![47i64; 8];
let nonce1 = b"nonce1";
let nonce2 = b"nonce2";

let (ciphertext1, proof1) = vahe.verifiable_encrypt(&plaintext, &pk, nonce1, &mut prng)?;
let (ciphertext2, _) = vahe.verifiable_encrypt(&plaintext, &pk, nonce2, &mut prng)?;

let items = vec![
(&proof1, &ciphertext1.component_a, nonce1 as &[u8]),
(&proof1, &ciphertext2.component_a, nonce2 as &[u8]), // Using proof1 for ciphertext2
];

let status = vahe.verify_multiple_encrypts(&items);
assert!(status.is_err());
Ok(())
}

#[gtest]
fn test_verifiable_partial_dec() -> googletest::Result<()> {
let vahe = ShellVahe::new(make_ahe_config(), CONTEXT_STRING)?;
Expand Down
8 changes: 8 additions & 0 deletions willow/src/traits/vahe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ pub trait EncryptVerify: VaheBase {
ciphertext: &Self::PartialDecCiphertext,
nonce: &[u8],
) -> Status;

/// Verify that multiple encryption proofs are valid.
///
/// `nonce` must match the nonce passed to `verifiable_encrypt` for each proof.
fn verify_multiple_encrypts(
&self,
items: &[(&Self::EncryptionProof, &Self::PartialDecCiphertext, &[u8])],
) -> Status;
}

pub trait VerifiablePartialDec: VaheBase {
Expand Down
10 changes: 5 additions & 5 deletions willow/src/zk/linear_ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct LinearInnerProductParameters {
F: RistrettoPoint,
F_: RistrettoPoint,
G: Vec<RistrettoPoint>,
seed: Vec<u8>,
}

pub fn inner_product(a: &[Scalar], b: &[Scalar]) -> Scalar {
Expand All @@ -59,6 +60,7 @@ fn common_setup(length: usize, parameter_seed: &[u8]) -> LinearInnerProductParam
)
})
.collect(),
seed: parameter_seed.to_vec(),
}
}

Expand All @@ -67,11 +69,9 @@ fn append_params_to_transcript(
params: &LinearInnerProductParameters,
) {
transcript.append_u64(b"n", params.n as u64);
for G_i in &params.G {
transcript.append_message(b"G_i", G_i.compress().as_bytes());
}
transcript.append_message(b"F", params.F.compress().as_bytes());
transcript.append_message(b"F_", params.F_.compress().as_bytes());
// We append the seed not the resulting params themselves because appending that many params
// more than doubles the run time of both prove and verify.
transcript.append_message(b"seed", &params.seed);
}

fn validate_and_append_point(
Expand Down
Loading