1use crate::transcript::Transcript;
98use bytes::{Buf, BufMut};
99use commonware_codec::{Encode, EncodeSize, Error, Read, Write};
100use commonware_math::{
101 algebra::{CryptoGroup, Field, Random, Space},
102 synthetic::Synthetic,
103};
104use rand_core::CryptoRngCore;
105
106#[derive(Clone, Debug, PartialEq)]
111pub struct Setup<G> {
112 pub value_generator: G,
114 pub blinding_generator: G,
116}
117
118impl<G: Write> Write for Setup<G> {
119 fn write(&self, buf: &mut impl BufMut) {
120 self.value_generator.write(buf);
121 self.blinding_generator.write(buf);
122 }
123}
124
125impl<G: EncodeSize> EncodeSize for Setup<G> {
126 fn encode_size(&self) -> usize {
127 self.value_generator.encode_size() + self.blinding_generator.encode_size()
128 }
129}
130
131impl<G: Read> Read for Setup<G>
132where
133 G::Cfg: Clone,
134{
135 type Cfg = G::Cfg;
136
137 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
138 Ok(Self {
139 value_generator: G::read_cfg(buf, cfg)?,
140 blinding_generator: G::read_cfg(buf, cfg)?,
141 })
142 }
143}
144
145#[cfg(any(test, feature = "arbitrary"))]
146impl<G> arbitrary::Arbitrary<'_> for Setup<G>
147where
148 G: for<'a> arbitrary::Arbitrary<'a>,
149{
150 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
151 Ok(Self {
152 value_generator: u.arbitrary()?,
153 blinding_generator: u.arbitrary()?,
154 })
155 }
156}
157
158#[derive(Clone, Debug, PartialEq)]
160pub struct Witness<F> {
161 pub value: F,
162 pub blinding: F,
163}
164
165impl<F> Witness<F> {
166 pub fn claim<G: Space<F>>(&self, setup: &Setup<G>) -> Claim<G> {
168 let plain = setup.value_generator.clone() * &self.value;
169 Claim {
170 pedersen: plain.clone() + &(setup.blinding_generator.clone() * &self.blinding),
171 plain,
172 }
173 }
174}
175
176#[derive(Clone, Debug, PartialEq)]
178pub struct Claim<G> {
179 pub plain: G,
180 pub pedersen: G,
181}
182
183impl<G: Write> Write for Claim<G> {
184 fn write(&self, buf: &mut impl BufMut) {
185 self.plain.write(buf);
186 self.pedersen.write(buf);
187 }
188}
189
190impl<G: EncodeSize> EncodeSize for Claim<G> {
191 fn encode_size(&self) -> usize {
192 self.plain.encode_size() + self.pedersen.encode_size()
193 }
194}
195
196impl<G: Read> Read for Claim<G>
197where
198 G::Cfg: Clone,
199{
200 type Cfg = G::Cfg;
201
202 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
203 Ok(Self {
204 plain: G::read_cfg(buf, cfg)?,
205 pedersen: G::read_cfg(buf, cfg)?,
206 })
207 }
208}
209
210#[cfg(any(test, feature = "arbitrary"))]
211impl<G> arbitrary::Arbitrary<'_> for Claim<G>
212where
213 G: for<'a> arbitrary::Arbitrary<'a>,
214{
215 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
216 Ok(Self {
217 plain: u.arbitrary()?,
218 pedersen: u.arbitrary()?,
219 })
220 }
221}
222
223#[derive(Clone, Debug, PartialEq)]
225pub struct Proof<F, G> {
226 plain_mask: G,
227 pedersen_mask: G,
228 value_response: F,
229 blinding_response: F,
230}
231
232impl<F: Write, G: Write> Write for Proof<F, G> {
233 fn write(&self, buf: &mut impl BufMut) {
234 self.plain_mask.write(buf);
235 self.pedersen_mask.write(buf);
236 self.value_response.write(buf);
237 self.blinding_response.write(buf);
238 }
239}
240
241impl<F: EncodeSize, G: EncodeSize> EncodeSize for Proof<F, G> {
242 fn encode_size(&self) -> usize {
243 self.plain_mask.encode_size()
244 + self.pedersen_mask.encode_size()
245 + self.value_response.encode_size()
246 + self.blinding_response.encode_size()
247 }
248}
249
250impl<F: Read, G: Read> Read for Proof<F, G>
251where
252 G::Cfg: Clone,
253 F::Cfg: Clone,
254{
255 type Cfg = (G::Cfg, F::Cfg);
256
257 fn read_cfg(buf: &mut impl Buf, (g_cfg, f_cfg): &Self::Cfg) -> Result<Self, Error> {
258 Ok(Self {
259 plain_mask: G::read_cfg(buf, g_cfg)?,
260 pedersen_mask: G::read_cfg(buf, g_cfg)?,
261 value_response: F::read_cfg(buf, f_cfg)?,
262 blinding_response: F::read_cfg(buf, f_cfg)?,
263 })
264 }
265}
266
267#[cfg(any(test, feature = "arbitrary"))]
268impl<F, G> arbitrary::Arbitrary<'_> for Proof<F, G>
269where
270 F: for<'a> arbitrary::Arbitrary<'a>,
271 G: for<'a> arbitrary::Arbitrary<'a>,
272{
273 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
274 Ok(Self {
275 plain_mask: u.arbitrary()?,
276 pedersen_mask: u.arbitrary()?,
277 value_response: u.arbitrary()?,
278 blinding_response: u.arbitrary()?,
279 })
280 }
281}
282
283pub fn prove<F: Field + Random, G: CryptoGroup<Scalar = F> + Encode>(
291 rng: &mut impl CryptoRngCore,
292 transcript: &mut Transcript,
293 setup: &Setup<G>,
294 claim: &Claim<G>,
295 witness: &Witness<F>,
296) -> Proof<F, G>
297where
298 Claim<G>: Encode,
299{
300 transcript.commit(claim.encode());
324
325 let value_mask = F::random(&mut *rng);
326 let blinding_mask = F::random(&mut *rng);
327 let plain_mask = setup.value_generator.clone() * &value_mask;
328 let pedersen_mask = plain_mask.clone() + &(setup.blinding_generator.clone() * &blinding_mask);
329
330 transcript.commit(plain_mask.encode());
331 transcript.commit(pedersen_mask.encode());
332 let challenge = F::random(transcript.noise(b"challenge"));
333
334 Proof {
335 plain_mask,
336 pedersen_mask,
337 value_response: value_mask + &(challenge.clone() * &witness.value),
338 blinding_response: blinding_mask + &(challenge * &witness.blinding),
339 }
340}
341
342pub fn verify<F: Field + Random, G: CryptoGroup<Scalar = F> + Encode + PartialEq>(
346 rng: &mut impl CryptoRngCore,
347 transcript: &mut Transcript,
348 setup: &Setup<Synthetic<F, G>>,
349 claim: &Claim<G>,
350 proof: Proof<F, G>,
351) -> Synthetic<F, G>
352where
353 Claim<G>: Encode,
354{
355 let Proof {
356 plain_mask,
357 pedersen_mask,
358 value_response,
359 blinding_response,
360 } = proof;
361
362 transcript.commit(claim.encode());
363 transcript.commit(plain_mask.encode());
364 transcript.commit(pedersen_mask.encode());
365 let challenge = F::random(transcript.noise(b"challenge"));
366
367 let plain_valid = Synthetic::concrete([
368 (F::one(), plain_mask),
369 (challenge.clone(), claim.plain.clone()),
370 ]) - &(setup.value_generator.clone() * &value_response);
371 let pedersen_valid = Synthetic::concrete([
372 (F::one(), pedersen_mask),
373 (challenge, claim.pedersen.clone()),
374 ]) - &(setup.value_generator.clone() * &value_response)
375 - &(setup.blinding_generator.clone() * &blinding_response);
376 pedersen_valid + &(plain_valid * &F::random(&mut *rng))
377}
378
379#[cfg(all(test, feature = "arbitrary"))]
380mod conformance {
381 use super::{Claim, Proof, Setup};
382 use commonware_codec::conformance::CodecConformance;
383 use commonware_math::test::{F as TestF, G as TestG};
384
385 commonware_conformance::conformance_tests! {
386 CodecConformance<Setup<TestG>>,
387 CodecConformance<Claim<TestG>>,
388 CodecConformance<Proof<TestF, TestG>>,
389 }
390}
391
392#[commonware_macros::stability(ALPHA)]
393#[cfg(any(test, feature = "fuzz"))]
394pub mod fuzz {
395 use super::*;
396 use crate::bls12381::primitives::group::{Scalar as F, G1 as G};
397 use arbitrary::{Arbitrary, Unstructured};
398 use commonware_math::algebra::{Additive, CryptoGroup, HashToGroup};
399 use commonware_parallel::Sequential;
400 use commonware_utils::test_rng;
401 use std::sync::OnceLock;
402
403 const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_PEDERSEN_TO_PLAIN";
404 const BAD_NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_PEDERSEN_TO_PLAIN_BUT_DIFFERENT";
405
406 pub(super) fn test_setup() -> &'static Setup<G> {
407 static TEST_SETUP: OnceLock<Setup<G>> = OnceLock::new();
408 TEST_SETUP.get_or_init(|| Setup {
409 value_generator: G::generator(),
410 blinding_generator: G::hash_to_group(NAMESPACE, b"blinding generator"),
411 })
412 }
413
414 struct Prover<'a> {
415 setup: &'a Setup<G>,
416 claim: Claim<G>,
417 proof: Proof<F, G>,
418 bad_namespace: bool,
419 honest: bool,
420 }
421
422 impl<'a> Prover<'a> {
423 fn new(setup: &'a Setup<G>, value: F, blinding: F) -> Self {
424 let witness = Witness { value, blinding };
425 let claim = witness.claim(setup);
426 let proof = prove(
427 &mut test_rng(),
428 &mut Transcript::new(NAMESPACE),
429 setup,
430 &claim,
431 &witness,
432 );
433 Self {
434 setup,
435 claim,
436 proof,
437 bad_namespace: false,
438 honest: true,
439 }
440 }
441
442 #[allow(clippy::missing_const_for_fn)]
443 fn bad_namespace(&mut self) {
444 self.honest = false;
445 self.bad_namespace = true;
446 }
447
448 fn tweak_plain_claim(&mut self, delta: F) {
449 if delta == F::zero() {
450 return;
451 }
452 self.honest = false;
453 self.claim.plain += &(self.setup.value_generator * &delta);
454 }
455
456 fn tweak_pedersen_claim(&mut self, value_delta: F, blinding_delta: F) {
457 if value_delta == F::zero() && blinding_delta == F::zero() {
458 return;
459 }
460 self.honest = false;
461 self.claim.pedersen += &((self.setup.value_generator * &value_delta)
462 + &(self.setup.blinding_generator * &blinding_delta));
463 }
464
465 fn tweak_mask(&mut self, tweak_plain: bool, delta: G) {
466 if delta == G::zero() {
467 return;
468 }
469 self.honest = false;
470 if tweak_plain {
471 self.proof.plain_mask += δ
472 } else {
473 self.proof.pedersen_mask += δ
474 }
475 }
476
477 fn tweak_response(&mut self, tweak_value: bool, delta: F) {
478 if delta == F::zero() {
479 return;
480 }
481 self.honest = false;
482 if tweak_value {
483 self.proof.value_response += δ
484 } else {
485 self.proof.blinding_response += δ
486 }
487 }
488
489 #[allow(clippy::missing_const_for_fn)]
490 fn honest(&self) -> bool {
491 self.honest
492 }
493
494 fn verify(self, rng: &mut impl CryptoRngCore) -> bool {
495 let ns = if self.bad_namespace {
496 BAD_NAMESPACE
497 } else {
498 NAMESPACE
499 };
500 let [g, h] = Synthetic::generators_array();
501 verify(
502 rng,
503 &mut Transcript::new(ns),
504 &Setup {
505 value_generator: g,
506 blinding_generator: h,
507 },
508 &self.claim,
509 self.proof,
510 )
511 .eval(
512 &[self.setup.value_generator, self.setup.blinding_generator],
513 &Sequential,
514 ) == G::zero()
515 }
516 }
517
518 #[derive(Debug)]
519 pub struct Plan {
520 value: F,
521 blinding: F,
522 }
523
524 impl<'a> Arbitrary<'a> for Plan {
525 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
526 Ok(Self {
527 value: u.arbitrary()?,
528 blinding: u.arbitrary()?,
529 })
530 }
531 }
532
533 impl Plan {
534 pub fn run(self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
535 let setup = test_setup();
536 let mut prover = Prover::new(setup, self.value, self.blinding);
537 if u.arbitrary::<bool>()? {
538 match u.arbitrary::<u8>()? {
539 x if x < 51 => prover.tweak_plain_claim(u.arbitrary()?),
540 x if x < 102 => prover.tweak_pedersen_claim(u.arbitrary()?, u.arbitrary()?),
541 x if x < 153 => prover.tweak_mask(u.arbitrary()?, u.arbitrary()?),
542 x if x < 204 => prover.tweak_response(u.arbitrary()?, u.arbitrary()?),
543 _ => prover.bad_namespace(),
544 }
545 }
546 match (prover.honest(), prover.verify(&mut test_rng())) {
547 (true, true) | (false, false) => {}
548 (true, false) => panic!("prover honest, but proof didn't verify"),
549 (false, true) => panic!("prover malicious, but proof verifies"),
550 }
551 Ok(())
552 }
553 }
554
555 #[test]
556 fn prover_tweaks_cover_noops_and_failures() {
557 let setup = test_setup();
558
559 let mut honest = Prover::new(setup, F::from(3u64), F::from(5u64));
560 honest.tweak_plain_claim(F::zero());
561 honest.tweak_pedersen_claim(F::zero(), F::zero());
562 honest.tweak_mask(true, G::zero());
563 honest.tweak_response(false, F::zero());
564 assert!(honest.honest());
565 assert!(honest.verify(&mut test_rng()));
566
567 type Tweak = Box<dyn FnOnce(&mut Prover<'static>)>;
568 let failures: [Tweak; 5] = [
569 Box::new(|p| p.tweak_plain_claim(F::from(1u64))),
570 Box::new(|p| p.tweak_pedersen_claim(F::from(1u64), F::from(1u64))),
571 Box::new(|p| p.tweak_mask(false, G::generator())),
572 Box::new(|p| p.tweak_response(true, F::from(1u64))),
573 Box::new(|p| p.bad_namespace()),
574 ];
575 for tweak in failures {
576 let mut prover = Prover::new(setup, F::from(3u64), F::from(5u64));
577 tweak(&mut prover);
578 assert!(!prover.honest());
579 assert!(!prover.verify(&mut test_rng()));
580 }
581 }
582}
583
584#[cfg(test)]
585mod test {
586 use super::{fuzz, Claim, Proof, Setup};
587 use commonware_codec::{Decode, Encode};
588 use commonware_invariants::minifuzz;
589 use commonware_math::test::{F, G};
590
591 fn assert_setup_roundtrip(setup: &Setup<G>) {
592 let encoded = setup.encode();
593 let decoded: Setup<G> =
594 Setup::decode_cfg(encoded.clone(), &()).expect("setup should decode with unit cfg");
595 assert_eq!(setup, &decoded);
596 assert_eq!(decoded.encode(), encoded);
597 }
598
599 fn assert_claim_roundtrip(claim: &Claim<G>) {
600 let encoded = claim.encode();
601 let decoded: Claim<G> =
602 Claim::decode_cfg(encoded.clone(), &()).expect("claim should decode with unit cfg");
603 assert_eq!(claim, &decoded);
604 assert_eq!(decoded.encode(), encoded);
605 }
606
607 fn assert_proof_roundtrip(proof: &Proof<F, G>) {
608 let encoded = proof.encode();
609 let decoded: Proof<F, G> = Proof::decode_cfg(encoded.clone(), &((), ()))
610 .expect("proof should decode with unit cfg");
611 assert_eq!(proof, &decoded);
612 assert_eq!(decoded.encode(), encoded);
613 }
614
615 #[test]
616 fn test_codec_roundtrip() {
617 minifuzz::test(|u| {
618 assert_setup_roundtrip(&u.arbitrary::<Setup<G>>()?);
619 assert_claim_roundtrip(&u.arbitrary::<Claim<G>>()?);
620 assert_proof_roundtrip(&u.arbitrary::<Proof<F, G>>()?);
621 Ok(())
622 });
623 }
624
625 #[test]
626 fn test_fuzz() {
627 minifuzz::test(|u| {
628 u.arbitrary::<fuzz::Plan>()?.run(u)?;
629 Ok(())
630 });
631 }
632}