Skip to content
Merged
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
133 changes: 133 additions & 0 deletions tests/encryption_gadget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,136 @@ fn incorrect_public_input_fails() -> Result<(), Error> {

Ok(())
}

#[derive(Debug)]
struct DecryptionCircuit<const L: usize> {
pub cipher: Vec<BlsScalar>,
pub message: [BlsScalar; L],
pub shared_secret: JubJubAffine,
pub nonce: BlsScalar,
}

impl<const L: usize> DecryptionCircuit<L> {
pub fn random(rng: &mut StdRng) -> Self {
let EncryptionCircuit {
message,
cipher,
shared_secret,
nonce,
} = EncryptionCircuit::<L>::random(rng);

Self {
cipher,
message,
shared_secret,
nonce,
}
}
}

impl<const L: usize> Default for DecryptionCircuit<L> {
fn default() -> Self {
Self {
cipher: vec![BlsScalar::zero(); L + 1],
message: [BlsScalar::zero(); L],
shared_secret: JubJubAffine::identity(),
nonce: BlsScalar::zero(),
}
}
}

impl<const L: usize> Circuit for DecryptionCircuit<L> {
fn circuit(&self, composer: &mut Composer) -> Result<(), PlonkError> {
// the cipher-text enters the circuit as private witnesses, so only the
// gadget's own constraints can reject it
let cipher_wit: Vec<Witness> = self
.cipher
.iter()
.map(|c| composer.append_witness(*c))
.collect();
let secret_wit = composer.append_point(self.shared_secret)?;
let nonce_wit = composer.append_witness(self.nonce);

let message_result =
decrypt_gadget(composer, &cipher_wit, &secret_wit, &nonce_wit)
.expect("decryption should pass");

// expose the decrypted message as public inputs
assert_eq!(message_result.len(), L);
message_result
.iter()
.zip(self.message)
.for_each(|(r, m)| composer.assert_equal_constant(*r, 0, Some(m)));

Ok(())
}
}

#[test]
fn decrypt_gadget_rejects_forged_cipher_texts() -> Result<(), Error> {
let mut rng = StdRng::seed_from_u64(0x42424242);
const MESSAGE_LEN: usize = 3;

let (prover, verifier) = Compiler::compile::<DecryptionCircuit<MESSAGE_LEN>>(
&PUB_PARAMS,
LABEL,
)?;

// the honest cipher-text decrypts in-circuit
let honest = DecryptionCircuit::<MESSAGE_LEN>::random(&mut rng);
let (proof, public_inputs) = prover.prove(&mut rng, &honest)?;
assert_eq!(public_inputs, honest.message);
verifier.verify(&proof, &public_inputs)?;

// A forged tag leaves the decrypted message unchanged, and shifting a
// cipher-text element shifts the decrypted message by the same amount.
// Both forgeries keep the claimed message consistent with the
// cipher-text, so only the in-circuit tag check can reject them.
let mut forged_tag = DecryptionCircuit::<MESSAGE_LEN>::random(&mut rng);
forged_tag.cipher[MESSAGE_LEN] += BlsScalar::one();

let mut forged_element = DecryptionCircuit::<MESSAGE_LEN>::random(&mut rng);
forged_element.cipher[1] += BlsScalar::one();
forged_element.message[1] += BlsScalar::one();

for forged in [forged_tag, forged_element] {
assert_eq!(
prover.prove(&mut rng, &forged).err(),
Some(Error::CircuitUnsatisfied),
"the decryption gadget must reject a cipher-text with a forged tag"
);
}

Ok(())
}

/// Pins the gate counts of the encryption gadgets for a message of three
/// elements. A changed count changes the verifier key of every circuit that
/// uses the gadgets, so update these values only for an intended layout
/// change.
#[test]
fn encryption_gadget_constraint_counts() {
let count = |decrypt: bool| {
let mut composer = Composer::initialized();
let input: Vec<Witness> = (0..4u64)
.map(|i| composer.append_witness(BlsScalar::from(i)))
.collect();
let shared_secret = composer
.append_point(JubJubAffine::identity())
.expect("the identity is a valid point");
let nonce = composer.append_witness(BlsScalar::zero());

let gates = composer.constraints();
if decrypt {
decrypt_gadget(&mut composer, &input, &shared_secret, &nonce)
.expect("decryption should pass");
} else {
encrypt_gadget(&mut composer, &input[..3], &shared_secret, &nonce)
.expect("encryption should pass");
}
Comment thread
HDauven marked this conversation as resolved.
Resolved
composer.constraints() - gates
};

assert_eq!(count(false), 1310);
assert_eq!(count(true), 1311);
}
38 changes: 38 additions & 0 deletions tests/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,41 @@ fn hash_gadget_constraint_counts() {
assert_eq!(count(false), 655);
assert_eq!(count(true), 743);
}

#[test]
fn gadget_output_len_only_overrides_the_other_domain() {
let input: [BlsScalar; 4] =
core::array::from_fn(|i| BlsScalar::from(i as u64 + 1));

// the witness values of the gadget output for a requested output length
let gadget_digest = |domain: Domain, input: &[BlsScalar], len: usize| {
let mut composer = Composer::initialized();
let input: Vec<Witness> =
input.iter().map(|i| composer.append_witness(*i)).collect();

let mut hash = HashGadget::new(domain);
hash.update(&input);
hash.output_len(len);
let output = hash.finalize(&mut composer);

output.iter().map(|w| composer[*w]).collect::<Vec<_>>()
};

// a zero output length is ignored, keeping the default single element
assert_eq!(
gadget_digest(Domain::Other, &input, 0),
Hash::digest(Domain::Other, &input)
);

// the merkle and encryption domains always output a single element
for (domain, input) in [
(Domain::Merkle2, &input[..2]),
(Domain::Merkle4, &input[..]),
(Domain::Encryption, &input[..]),
] {
assert_eq!(
gadget_digest(domain, input, 3),
Hash::digest(domain, input)
);
}
}
21 changes: 21 additions & 0 deletions tests/hash_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,24 @@ fn finalize_panics_on_empty_chunk() {
hash.update(&[]);
hash.finalize();
}

#[test]
fn output_len_only_overrides_the_other_domain() {
// a zero output length is ignored, keeping the default single element
let mut hash = Hash::new(Domain::Other);
hash.update(&INPUT);
hash.output_len(0);
assert_eq!(hash.try_finalize(), Ok(Hash::digest(Domain::Other, &INPUT)));

// the merkle and encryption domains always output a single element
for (domain, input) in [
(Domain::Merkle2, &INPUT[..2]),
(Domain::Merkle4, &INPUT[..]),
(Domain::Encryption, &INPUT[..]),
] {
let mut hash = Hash::new(domain);
hash.update(input);
hash.output_len(3);
assert_eq!(hash.try_finalize(), Ok(Hash::digest(domain, input)));
}
}
Loading