coconut_crypto/helpers/
with_schnorr_response.rs

1use alloc::vec::Vec;
2use ark_ec::AffineRepr;
3use ark_serialize::*;
4use core::{cmp::Ordering, ops::Range};
5use serde::{de::DeserializeOwned, Deserialize, Serialize};
6use serde_with::serde_as;
7use utils::{aliases::CanonicalSerDe, serde_utils::ArkObjectBytes};
8
9use schnorr_pok::{
10    error::SchnorrError, SchnorrChallengeContributor, SchnorrCommitment, SchnorrResponse,
11};
12
13use super::WithSchnorrAndBlindings;
14
15/// Combines value with the `t` commitment from `SchnorrCommitment` **excluding blindings**.
16#[serde_as]
17#[derive(
18    Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize,
19)]
20#[serde(bound = "V: Serialize + DeserializeOwned")]
21pub struct WithSchnorrResponse<G: AffineRepr, V: CanonicalSerDe> {
22    #[serde_as(as = "ArkObjectBytes")]
23    pub commitment: G,
24    pub response: SchnorrResponse<G>,
25    pub committed_message_indices: IndiceRange,
26    pub value: V,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
30pub struct IndiceRange(Range<usize>);
31utils::impl_deref! { IndiceRange(Range<usize>) }
32
33impl CanonicalSerialize for IndiceRange {
34    fn serialized_size(&self, compress: Compress) -> usize {
35        self.start.serialized_size(compress) + self.end.serialized_size(compress)
36    }
37
38    fn serialize_with_mode<W: Write>(
39        &self,
40        mut writer: W,
41        compress: Compress,
42    ) -> Result<(), SerializationError> {
43        self.start.serialize_with_mode(&mut writer, compress)?;
44        self.end.serialize_with_mode(&mut writer, compress)?;
45
46        Ok(())
47    }
48}
49
50impl Valid for IndiceRange {
51    fn check(&self) -> Result<(), SerializationError> {
52        if self.start > self.end {
53            Err(SerializationError::InvalidData)
54        } else {
55            Ok(())
56        }
57    }
58}
59
60impl CanonicalDeserialize for IndiceRange {
61    fn deserialize_with_mode<R: Read>(
62        mut reader: R,
63        compress: Compress,
64        validate: Validate,
65    ) -> Result<Self, SerializationError> {
66        let start = usize::deserialize_with_mode(&mut reader, compress, validate)?;
67        let end = usize::deserialize_with_mode(&mut reader, compress, validate)?;
68
69        Ok(Self(start..end))
70    }
71}
72
73impl<G, V> WithSchnorrResponse<G, V>
74where
75    G: AffineRepr,
76    V: CanonicalSerDe + Clone,
77{
78    /// Combines value with the `SchnorrResponse` and omits blindings.
79    pub fn new(
80        response: SchnorrResponse<G>,
81        &WithSchnorrAndBlindings {
82            schnorr: SchnorrCommitment { t, .. },
83            ref value,
84        }: &WithSchnorrAndBlindings<G, V>,
85        committed_message_indices: Range<usize>,
86    ) -> Self {
87        WithSchnorrResponse {
88            response,
89            commitment: t,
90            value: value.clone(),
91            committed_message_indices: IndiceRange(committed_message_indices),
92        }
93    }
94}
95
96impl<G, V> SchnorrChallengeContributor for WithSchnorrResponse<G, V>
97where
98    G: AffineRepr,
99    V: CanonicalSerDe,
100{
101    /// The commitment's contribution to the overall challenge of the protocol.
102    fn challenge_contribution<W: Write>(&self, mut writer: W) -> Result<(), SchnorrError> {
103        self.value
104            .serialize_compressed(&mut writer)
105            .map_err(SchnorrError::Serialization)?;
106        self.commitment
107            .serialize_compressed(&mut writer)
108            .map_err(SchnorrError::Serialization)
109    }
110}
111
112impl<G, V> WithSchnorrResponse<G, V>
113where
114    G: AffineRepr,
115    V: CanonicalSerDe,
116{
117    /// Get the response from post-challenge phase of the Schnorr protocol for the given
118    /// message index `msg_idx`. Used when comparing message equality.
119    /// It's the caller's responsibility to ensure that indices are unique.
120    pub(crate) fn response_for_message(
121        &self,
122        msg_idx: usize,
123        unique_revealed_msg_indices: impl IntoIterator<Item = usize>,
124    ) -> Result<&G::ScalarField, SchnorrError> {
125        let mut adjusted_msg_idx = msg_idx;
126
127        let out_of_bounds = move || {
128            SchnorrError::IndexOutOfBounds(
129                msg_idx,
130                self.committed_message_indices
131                    .end
132                    .saturating_sub(self.committed_message_indices.start),
133            )
134        };
135
136        for idx in unique_revealed_msg_indices {
137            match idx.cmp(&msg_idx) {
138                Ordering::Equal => Err(SchnorrError::InvalidResponse)?,
139                Ordering::Less => {
140                    adjusted_msg_idx = adjusted_msg_idx.checked_sub(1).ok_or_else(out_of_bounds)?
141                }
142                _ => {}
143            }
144        }
145
146        self.committed_message_indices
147            .start
148            .checked_add(adjusted_msg_idx)
149            .ok_or_else(out_of_bounds)
150            .and_then(|adjusted_msg_idx_with_applied_offset| {
151                self.response
152                    .get_response(adjusted_msg_idx_with_applied_offset)
153            })
154    }
155}