Skip to content

Commit 7e1c171

Browse files
Merge pull request #20 from Chausseaumoine/feat/morphism-absorption
feat: absorb morphism into Fiat-Shamir transcript for SchnorrProtocol A git force-push was done to adapt to the refactorization of GroupMorphism -> LinearRelation, which happened during the PR development.
2 parents 9e348fb + 202726e commit 7e1c171

File tree

10 files changed

+79
-119
lines changed

10 files changed

+79
-119
lines changed

src/composition.rs

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use crate::codec::Codec;
1515
use crate::traits::CompactProtocol;
1616
use crate::{
1717
errors::Error,
18-
fiat_shamir::{FiatShamir, HasGroupMorphism},
19-
linear_relation::LinearRelation,
18+
fiat_shamir::FiatShamir,
2019
group_serialization::{deserialize_scalar, serialize_scalar},
20+
linear_relation::LinearRelation,
2121
schnorr_protocol::SchnorrProtocol,
2222
traits::{SigmaProtocol, SigmaProtocolSimulator},
2323
};
@@ -637,17 +637,19 @@ where
637637
G: Group + GroupEncoding,
638638
C: Codec<Challenge = ProtocolChallenge<G>>,
639639
{
640-
fn push_commitment(&self, codec: &mut C, commitment: &Self::Commitment) {
640+
fn absorb_statement_and_commitment(&self, codec: &mut C, commitment: &Self::Commitment) {
641641
match (self, commitment) {
642-
(Protocol::Simple(p), ProtocolCommitment::Simple(c)) => p.push_commitment(codec, c),
642+
(Protocol::Simple(p), ProtocolCommitment::Simple(c)) => {
643+
p.absorb_statement_and_commitment(codec, c)
644+
}
643645
(Protocol::And(ps), ProtocolCommitment::And(cs)) => {
644646
for (i, p) in ps.iter().enumerate() {
645-
p.push_commitment(codec, &cs[i]);
647+
p.absorb_statement_and_commitment(codec, &cs[i]);
646648
}
647649
}
648650
(Protocol::Or(ps), ProtocolCommitment::Or(cs)) => {
649651
for (i, p) in ps.iter().enumerate() {
650-
p.push_commitment(codec, &cs[i]);
652+
p.absorb_statement_and_commitment(codec, &cs[i]);
651653
}
652654
}
653655
_ => panic!(),
@@ -658,17 +660,3 @@ where
658660
Ok(codec.verifier_challenge())
659661
}
660662
}
661-
662-
impl<G: Group + GroupEncoding> HasGroupMorphism for Protocol<G> {
663-
fn absorb_morphism_structure<C: Codec>(&self, codec: &mut C) -> Result<(), Error> {
664-
match self {
665-
Protocol::Simple(p) => p.absorb_morphism_structure(codec),
666-
Protocol::And(ps) | Protocol::Or(ps) => {
667-
for p in ps {
668-
p.absorb_morphism_structure(codec)?
669-
}
670-
Ok(())
671-
}
672-
}
673-
}
674-
}

src/fiat_shamir.rs

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,17 @@ use rand::{CryptoRng, RngCore};
2222
/// deterministic challenge generation function.
2323
///
2424
/// Challenge generation occurs in two stages:
25-
/// - `push_commitment`: absorbs commitments to feed the codec
25+
/// - `absorb_statement_and_commitment`: absorbs commitments to feed the codec
2626
/// - `get_challenge`: extracts the challenge from the codec
2727
///
2828
/// # Type Parameters
2929
/// - `C`: the codec used for encoding/decoding messages to/from the IP space.
3030
pub trait FiatShamir<C: Codec>: SigmaProtocol {
31-
fn push_commitment(&self, codec: &mut C, commitment: &Self::Commitment);
31+
fn absorb_statement_and_commitment(&self, codec: &mut C, commitment: &Self::Commitment);
3232

3333
fn get_challenge(&self, codec: &mut C) -> Result<Self::Challenge, Error>;
3434
}
3535

36-
/// Structures implementing this trait must implicitly have an associated linear relation.
37-
///
38-
/// This trait allows the data of the morphisms underlying the structure to be absorbed into a codec.
39-
pub trait HasGroupMorphism {
40-
fn absorb_morphism_structure<C: Codec>(&self, codec: &mut C) -> Result<(), Error>;
41-
}
42-
4336
type Transcript<P> = (
4437
<P as SigmaProtocol>::Commitment,
4538
<P as SigmaProtocol>::Challenge,
@@ -116,7 +109,8 @@ where
116109

117110
let (commitment, prover_state) = self.sigmap.prover_commit(witness, rng)?;
118111
// Fiat Shamir challenge
119-
self.sigmap.push_commitment(&mut codec, &commitment);
112+
self.sigmap
113+
.absorb_statement_and_commitment(&mut codec, &commitment);
120114
let challenge = self.sigmap.get_challenge(&mut codec)?;
121115
// Prover's response
122116
let response = self.sigmap.prover_response(prover_state, &challenge)?;
@@ -149,7 +143,8 @@ where
149143
let mut codec = self.hash_state.clone();
150144

151145
// Recompute the challenge
152-
self.sigmap.push_commitment(&mut codec, commitment);
146+
self.sigmap
147+
.absorb_statement_and_commitment(&mut codec, commitment);
153148
let expected_challenge = self.sigmap.get_challenge(&mut codec)?;
154149
// Verification of the proof
155150
match *challenge == expected_challenge {
@@ -199,7 +194,8 @@ where
199194
let mut codec = self.hash_state.clone();
200195

201196
// Recompute the challenge
202-
self.sigmap.push_commitment(&mut codec, &commitment);
197+
self.sigmap
198+
.absorb_statement_and_commitment(&mut codec, &commitment);
203199
let challenge = self.sigmap.get_challenge(&mut codec)?;
204200
// Verification of the proof
205201
self.sigmap.verifier(&commitment, &challenge, &response)
@@ -259,15 +255,3 @@ where
259255
self.verify(&commitment, &challenge, &response)
260256
}
261257
}
262-
263-
impl<P, C> NISigmaProtocol<P, C>
264-
where
265-
P: SigmaProtocol + HasGroupMorphism + FiatShamir<C>,
266-
P::Challenge: PartialEq,
267-
C: Codec<Challenge = P::Challenge> + Clone,
268-
{
269-
/// Absorbs the morphism structure into the transcript codec.
270-
pub fn absorb_morphism(&self, codec: &mut C) -> Result<(), Error> {
271-
self.sigmap.absorb_morphism_structure::<C>(codec)
272-
}
273-
}

src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
pub mod composition;
1616
pub mod errors;
1717
pub mod fiat_shamir;
18-
pub mod linear_relation;
1918
pub mod group_serialization;
19+
pub mod linear_relation;
2020
pub mod schnorr_protocol;
2121
pub mod traits;
2222

@@ -25,5 +25,4 @@ pub mod codec;
2525
#[cfg(test)]
2626
pub mod tests;
2727

28-
29-
pub use linear_relation::LinearRelation;
28+
pub use linear_relation::LinearRelation;

src/linear_relation.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
1111
use crate::errors::Error;
1212
use group::{Group, GroupEncoding};
13-
use std::iter;
13+
use std::{io::Write, iter};
1414

1515
/// Implementations of core ops for the linear combination types.
1616
mod ops;
@@ -161,6 +161,13 @@ impl<G: Group> GroupMap<G> {
161161
.enumerate()
162162
.filter_map(|(i, x)| x.map(|x| (GroupVar(i), x)))
163163
}
164+
165+
pub fn iter(&self) -> impl Iterator<Item = (GroupVar, &G)> {
166+
self.0
167+
.iter()
168+
.enumerate()
169+
.filter_map(|(i, opt)| opt.as_ref().map(|g| (GroupVar(i), g)))
170+
}
164171
}
165172

166173
impl<G> Default for GroupMap<G> {
@@ -455,4 +462,42 @@ where
455462
.map(|&var| self.morphism.group_elements.get(var))
456463
.collect()
457464
}
465+
466+
/// Returns a binary label describing the morphism structure, following the Signal POKSHO format.
467+
///
468+
/// The format is:
469+
/// - [Ne: u8] number of equations
470+
/// - For each equation:
471+
/// - [output_point_index: u8]
472+
/// - [Nt: u8] number of terms
473+
/// - Nt × [scalar_index: u8, point_index: u8] term entries
474+
pub fn label(&self) -> Vec<u8> {
475+
let mut out = Vec::new();
476+
477+
// 1. Number of equations (must match image vector length)
478+
let ne = self.image.len();
479+
assert_eq!(
480+
ne,
481+
self.morphism.constraints.len(),
482+
"Number of equations and image variables must match"
483+
);
484+
out.write_all(&[ne as u8]).unwrap();
485+
486+
// 2. Encode each equation with its LHS and terms
487+
for (constraint, output_var) in self.morphism.constraints.iter().zip(self.image.iter()) {
488+
// a. Output point index (LHS)
489+
out.write_all(&[output_var.index() as u8]).unwrap();
490+
491+
// b. Number of terms in the linear combination
492+
let terms = constraint.terms();
493+
out.write_all(&[terms.len() as u8]).unwrap();
494+
495+
// c. Each term: (scalar_index, point_index)
496+
for term in terms {
497+
out.write_all(&[term.scalar().index() as u8]).unwrap();
498+
out.write_all(&[term.elem().index() as u8]).unwrap();
499+
}
500+
}
501+
out
502+
}
458503
}

src/schnorr_protocol.rs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
use crate::codec::Codec;
88
use crate::errors::Error;
9-
use crate::fiat_shamir::{FiatShamir, HasGroupMorphism};
9+
use crate::fiat_shamir::FiatShamir;
1010
use crate::linear_relation::LinearRelation;
1111
use crate::{
1212
group_serialization::*,
@@ -413,16 +413,18 @@ where
413413
C: Codec<Challenge = <G as Group>::Scalar>,
414414
G: Group + GroupEncoding,
415415
{
416-
/// Absorbs commitments into the codec for future use of the codec
416+
/// Absorbs statement and commitment into the codec
417417
///
418418
/// # Parameters
419419
/// - `codec`: the Codec that absorbs commitments
420420
/// - `commitment`: a commitment of SchnorrProtocol
421-
fn push_commitment(&self, codec: &mut C, commitment: &Self::Commitment) {
422-
let mut data = Vec::new();
421+
fn absorb_statement_and_commitment(&self, codec: &mut C, commitment: &Self::Commitment) {
422+
let mut data = self.0.label();
423+
423424
for commit in commitment {
424425
data.extend_from_slice(commit.to_bytes().as_ref());
425426
}
427+
426428
codec.prover_message(&data);
427429
}
428430

@@ -437,17 +439,3 @@ where
437439
Ok(codec.verifier_challenge())
438440
}
439441
}
440-
441-
impl<G: Group + GroupEncoding> HasGroupMorphism for SchnorrProtocol<G> {
442-
fn absorb_morphism_structure<C: Codec>(&self, codec: &mut C) -> Result<(), Error> {
443-
for lc in &self.0.morphism.constraints {
444-
for term in lc.terms() {
445-
let mut buf = [0u8; 16];
446-
buf[..8].copy_from_slice(&(term.scalar().index() as u64).to_le_bytes());
447-
buf[8..].copy_from_slice(&(term.elem().index() as u64).to_le_bytes());
448-
codec.prover_message(&buf);
449-
}
450-
}
451-
Ok(())
452-
}
453-
}

src/tests/composition.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use super::test_utils::{
88
};
99
use crate::codec::ShakeCodec;
1010
use crate::composition::{Protocol, ProtocolWitness};
11-
use crate::fiat_shamir::{HasGroupMorphism, NISigmaProtocol};
11+
use crate::fiat_shamir::NISigmaProtocol;
1212
use crate::schnorr_protocol::SchnorrProtocol;
1313

1414
type G = RistrettoPoint;
@@ -83,13 +83,9 @@ fn composition_proof_correct() {
8383
let protocol = Protocol::And(vec![or_protocol1, simple_protocol1, and_protocol1]);
8484
let witness = ProtocolWitness::And(vec![or_witness1, simple_witness1, and_witness1]);
8585

86-
let mut nizk =
86+
let nizk =
8787
NISigmaProtocol::<Protocol<RistrettoPoint>, ShakeCodec<G>>::new(domain_sep, protocol);
8888

89-
nizk.sigmap
90-
.absorb_morphism_structure(&mut nizk.hash_state)
91-
.unwrap();
92-
9389
// Batchable and compact proofs
9490
let proof_batchable_bytes = nizk.prove_batchable(&witness, &mut rng).unwrap();
9591
let proof_compact_bytes = nizk.prove_compact(&witness, &mut rng).unwrap();

src/tests/composition_protocol.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use super::test_utils::{
88
};
99
use crate::codec::ShakeCodec;
1010
use crate::composition::{Protocol, ProtocolWitness};
11-
use crate::fiat_shamir::{HasGroupMorphism, NISigmaProtocol};
11+
use crate::fiat_shamir::NISigmaProtocol;
1212
use crate::schnorr_protocol::SchnorrProtocol;
1313

1414
type G = RistrettoPoint;
@@ -83,13 +83,9 @@ fn composition_proof_correct() {
8383
let protocol = Protocol::And(vec![or_protocol1, simple_protocol1, and_protocol1]);
8484
let witness = ProtocolWitness::And(vec![or_witness1, simple_witness1, and_witness1]);
8585

86-
let mut nizk =
86+
let nizk =
8787
NISigmaProtocol::<Protocol<RistrettoPoint>, ShakeCodec<G>>::new(domain_sep, protocol);
8888

89-
nizk.sigmap
90-
.absorb_morphism_structure(&mut nizk.hash_state)
91-
.unwrap();
92-
9389
// Batchable and compact proofs
9490
let proof_batchable_bytes = nizk.prove_batchable(&witness, &mut rng).unwrap();
9591
let proof_compact_bytes = nizk.prove_compact(&witness, &mut rng).unwrap();

src/tests/relations.rs

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use bls12_381::{G1Projective as G, Scalar};
22
use group::{Group, ff::Field};
33
use rand::rngs::OsRng;
44

5-
use crate::fiat_shamir::{HasGroupMorphism, NISigmaProtocol};
5+
use crate::fiat_shamir::NISigmaProtocol;
66
use crate::tests::test_utils::{
77
bbs_blind_commitment_computation, discrete_logarithm, dleq, pedersen_commitment,
88
pedersen_commitment_dleq,
@@ -97,43 +97,6 @@ fn noninteractive_discrete_logarithm() {
9797
);
9898
}
9999

100-
/// This part tests the implementation of the SigmaProtocol trait for the
101-
/// SchnorrProtocol structure as well as the Fiat-Shamir NISigmaProtocol transform,
102-
/// with additional morphism structure absorption into the transcript.
103-
#[test]
104-
fn noninteractive_discrete_logarithm_with_morphism_transcript() {
105-
let mut rng = OsRng;
106-
let (morphismp, witness) = discrete_logarithm(Scalar::random(&mut rng));
107-
108-
// The SigmaProtocol induced by morphismp
109-
let protocol = SchnorrProtocol::from(morphismp);
110-
111-
// Fiat-Shamir wrapper
112-
let domain_sep = b"test-fiat-shamir-schnorr";
113-
let mut nizk = NISigmaProtocol::<SchnorrProtocol<G>, ShakeCodec<G>>::new(domain_sep, protocol);
114-
115-
// Morphism absorption
116-
nizk.sigmap
117-
.absorb_morphism_structure(&mut nizk.hash_state)
118-
.unwrap();
119-
120-
// Generate and verify proofs
121-
let proof_batchable_bytes = nizk.prove_batchable(&witness, &mut rng).unwrap();
122-
let proof_compact_bytes = nizk.prove_compact(&witness, &mut rng).unwrap();
123-
124-
let verified_batchable = nizk.verify_batchable(&proof_batchable_bytes).is_ok();
125-
let verified_compact = nizk.verify_compact(&proof_compact_bytes).is_ok();
126-
127-
assert!(
128-
verified_batchable,
129-
"Fiat-Shamir Schnorr proof with morphism absorption failed (batchable)"
130-
);
131-
assert!(
132-
verified_compact,
133-
"Fiat-Shamir Schnorr proof with morphism absorption failed (compact)"
134-
);
135-
}
136-
137100
#[test]
138101
fn noninteractive_dleq() {
139102
let mut rng = OsRng;

src/tests/spec/custom_schnorr_protocol.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use rand::{CryptoRng, Rng};
55
use crate::codec::Codec;
66
use crate::errors::Error;
77
use crate::fiat_shamir::FiatShamir;
8-
use crate::linear_relation::LinearRelation;
98
use crate::group_serialization::*;
9+
use crate::linear_relation::LinearRelation;
1010
use crate::tests::spec::random::SRandom;
1111
use crate::traits::SigmaProtocol;
1212

@@ -156,7 +156,7 @@ where
156156
C: Codec<Challenge = <G as Group>::Scalar>,
157157
G: SRandom + GroupEncoding,
158158
{
159-
fn push_commitment(&self, codec: &mut C, commitment: &Self::Commitment) {
159+
fn absorb_statement_and_commitment(&self, codec: &mut C, commitment: &Self::Commitment) {
160160
let mut data = Vec::new();
161161
for commit in commitment {
162162
data.extend_from_slice(commit.to_bytes().as_ref());

src/traits.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use crate::errors::Error;
66
use rand::{CryptoRng, Rng};
77

8+
type BatchableProofResult<C, R> = Result<((C, R), usize), Error>;
89
/// A trait defining the behavior of a generic Sigma protocol.
910
///
1011
/// A Sigma protocol is a 3-message proof protocol where a prover can convince
@@ -74,7 +75,7 @@ pub trait SigmaProtocol {
7475
fn deserialize_batchable(
7576
&self,
7677
_data: &[u8],
77-
) -> Result<((Self::Commitment, Self::Response), usize), Error>;
78+
) -> BatchableProofResult<Self::Commitment, Self::Response>;
7879
}
7980

8081
/// A feature defining the behavior of a protocol for which it is possible to compact the proofs by omitting the commitments.

0 commit comments

Comments
 (0)