• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / catalyst-libs / 21453968213

28 Jan 2026 08:15PM UTC coverage: 70.114% (+0.004%) from 70.11%
21453968213

Pull #819

github

web-flow
Merge 7c0795d9d into eaa52b8e6
Pull Request #819: feat(rust/signed-doc): Contest Ballot `is_single` choice validation

125 of 130 new or added lines in 4 files covered. (96.15%)

20 existing lines in 3 files now uncovered.

16842 of 24021 relevant lines covered (70.11%)

2383.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.38
/rust/catalyst-voting/src/crypto/zk_unit_vector/decoding.rs
1
//! ZK Unit Vector objects decoding implementation
2

3
use cbork_utils::decode_helper::decode_array_len;
4
use minicbor::{Decode, Decoder, Encode, Encoder, encode::Write};
5

6
use super::{ResponseRandomness, UnitVectorProof};
7
use crate::crypto::{
8
    elgamal::Ciphertext,
9
    group::{GroupElement, Scalar},
10
    zk_unit_vector::randomness_announcements::Announcement,
11
};
12

13
/// A CBOR version of the `UnitVectorProof`.
14
const UNIT_VECTOR_PROOF_VERSION: u64 = 0;
15

16
/// A number of elements in the
17
/// `zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item` data type.
18
///
19
/// The CBOR CDDL schema:
20
/// ```cddl
21
/// zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item = ( zkproof-elgamal-announcement, ~elgamal-ristretto255-encrypted-choice, zkproof-ed25519-r-response )
22
///
23
/// zkproof-elgamal-announcement = ( zkproof-elgamal-group-element, zkproof-elgamal-group-element, zkproof-elgamal-group-element )
24
///
25
/// zkproof-elgamal-group-element = bytes .size 32
26
///
27
/// elgamal-ristretto255-encrypted-choice = [
28
///     c1: elgamal-ristretto255-group-element
29
///     c2: elgamal-ristretto255-group-element
30
/// ]
31
///
32
/// zkproof-ed25519-r-response = ( zkproof-ed25519-scalar, zkproof-ed25519-scalar, zkproof-ed25519-scalar )
33
///
34
/// zkproof-ed25519-scalar = bytes .size 32
35
/// ```
36
///
37
/// Therefore, the total number consists of the following:
38
/// - 8 (zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item)
39
///      - 3 (zkproof-elgamal-announcement = x3 zkproof-elgamal-group-element)
40
///      - 2 (elgamal-ristretto255-encrypted-choice = x2
41
///        elgamal-ristretto255-group-element)
42
///      - 3 (zkproof-ed25519-r-response = x3 zkproof-ed25519-scalar)
43
const ITEM_ELEMENTS_LEN: u64 = 8;
44

45
/// A minimal length of the underlying CBOR array of the `UnitVectorProof` data type.
46
///
47
/// The CBOR CDDL schema:
48
/// ```cddl
49
/// zkproof-elgamal-ristretto255-unit-vector-with-single-selection = [ +zkproof-elgamal-ristretto255-unit-vector-with-single-selection-item, zkproof-ed25519-scalar ]
50
/// ```
51
const MIN_PROOF_CBOR_ARRAY_LEN: u64 = ITEM_ELEMENTS_LEN + 1;
52

53
impl Encode<()> for Announcement {
54
    fn encode<W: Write>(
776✔
55
        &self,
776✔
56
        e: &mut Encoder<W>,
776✔
57
        ctx: &mut (),
776✔
58
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
776✔
59
        self.i.encode(e, ctx)?;
776✔
60
        self.b.encode(e, ctx)?;
776✔
61
        self.a.encode(e, ctx)
776✔
62
    }
776✔
63
}
64

65
impl Decode<'_, ()> for Announcement {
66
    fn decode(
776✔
67
        d: &mut Decoder<'_>,
776✔
68
        ctx: &mut (),
776✔
69
    ) -> Result<Self, minicbor::decode::Error> {
776✔
70
        let i = GroupElement::decode(d, ctx)?;
776✔
71
        let b = GroupElement::decode(d, ctx)?;
776✔
72
        let a = GroupElement::decode(d, ctx)?;
776✔
73
        Ok(Self { i, b, a })
776✔
74
    }
776✔
75
}
76

77
impl Encode<()> for ResponseRandomness {
78
    fn encode<W: Write>(
776✔
79
        &self,
776✔
80
        e: &mut Encoder<W>,
776✔
81
        ctx: &mut (),
776✔
82
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
776✔
83
        self.z.encode(e, ctx)?;
776✔
84
        self.w.encode(e, ctx)?;
776✔
85
        self.v.encode(e, ctx)
776✔
86
    }
776✔
87
}
88

89
impl Decode<'_, ()> for ResponseRandomness {
90
    fn decode(
776✔
91
        d: &mut Decoder<'_>,
776✔
92
        ctx: &mut (),
776✔
93
    ) -> Result<Self, minicbor::decode::Error> {
776✔
94
        let z = Scalar::decode(d, ctx)?;
776✔
95
        let w = Scalar::decode(d, ctx)?;
776✔
96
        let v = Scalar::decode(d, ctx)?;
776✔
97
        Ok(Self { z, w, v })
776✔
98
    }
776✔
99
}
100

101
impl Encode<()> for UnitVectorProof {
102
    fn encode<W: Write>(
516✔
103
        &self,
516✔
104
        e: &mut Encoder<W>,
516✔
105
        ctx: &mut (),
516✔
106
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
516✔
107
        if self.0.len() != self.1.len() || self.0.len() != self.2.len() {
516✔
108
            return Err(minicbor::encode::Error::message(format!(
×
109
                "All UnitVectorProof parts must have the same length: announcements = {}, choice = {}, responses = {}",
×
110
                self.0.len(),
×
111
                self.1.len(),
×
112
                self.2.len()
×
113
            )));
×
114
        }
516✔
115

116
        e.array(2)?;
516✔
117
        UNIT_VECTOR_PROOF_VERSION.encode(e, ctx)?;
516✔
118

119
        e.array(self.0.len() as u64 * ITEM_ELEMENTS_LEN + 1)?;
516✔
120
        for ((announcement, choice), response) in
520✔
121
            self.0.iter().zip(self.1.iter()).zip(self.2.iter())
516✔
122
        {
123
            announcement.encode(e, ctx)?;
520✔
124
            choice.first().encode(e, ctx)?;
520✔
125
            choice.second().encode(e, ctx)?;
520✔
126
            response.encode(e, ctx)?;
520✔
127
        }
128
        self.3.encode(e, ctx)
516✔
129
    }
516✔
130
}
131

132
impl Decode<'_, ()> for UnitVectorProof {
133
    fn decode(
516✔
134
        d: &mut Decoder<'_>,
516✔
135
        ctx: &mut (),
516✔
136
    ) -> Result<Self, minicbor::decode::Error> {
516✔
137
        let len = decode_array_len(d, "UnitVectorProof")?;
516✔
138
        if len != 2 {
516✔
139
            return Err(minicbor::decode::Error::message(format!(
×
140
                "Unexpected UnitVectorProof array length {len}, expected 2"
×
141
            )));
×
142
        }
516✔
143

144
        let version = u64::decode(d, ctx)?;
516✔
145
        if version != UNIT_VECTOR_PROOF_VERSION {
516✔
146
            return Err(minicbor::decode::Error::message(format!(
×
147
                "Unexpected UnitVectorProof version value: {version}, expected {UNIT_VECTOR_PROOF_VERSION}"
×
148
            )));
×
149
        }
516✔
150

151
        let len = decode_array_len(d, "UnitVectorProof inner array")?;
516✔
152
        if len < MIN_PROOF_CBOR_ARRAY_LEN
516✔
153
            || !len.saturating_sub(1).is_multiple_of(ITEM_ELEMENTS_LEN)
516✔
154
        {
UNCOV
155
            return Err(minicbor::decode::Error::message(format!(
×
UNCOV
156
                "Unexpected rUnitVectorProof inner array length {len}, expected multiplier of {MIN_PROOF_CBOR_ARRAY_LEN}"
×
UNCOV
157
            )));
×
158
        }
516✔
159

160
        let elements = len.saturating_sub(1) / ITEM_ELEMENTS_LEN;
516✔
161
        let mut announcements = Vec::with_capacity(elements as usize);
516✔
162
        let mut choices = Vec::with_capacity(elements as usize);
516✔
163
        let mut responses = Vec::with_capacity(elements as usize);
516✔
164

165
        for _ in 0..elements {
516✔
166
            announcements.push(Announcement::decode(d, ctx)?);
520✔
167
            let first = GroupElement::decode(d, ctx)?;
520✔
168
            let second = GroupElement::decode(d, ctx)?;
520✔
169
            choices.push(Ciphertext::from_elements(first, second));
520✔
170
            responses.push(ResponseRandomness::decode(d, ctx)?);
520✔
171
        }
172
        let scalar = Scalar::decode(d, ctx)?;
516✔
173

174
        Ok(Self(announcements, choices, responses, scalar))
516✔
175
    }
516✔
176
}
177

178
#[cfg(test)]
179
#[allow(clippy::explicit_deref_methods)]
180
mod tests {
181
    use proptest::property_test;
182

183
    use super::*;
184

185
    #[property_test]
186
    fn response_randomness_cbor_roundtrip(original: ResponseRandomness) {
187
        let mut buffer = Vec::new();
188
        original
189
            .encode(&mut Encoder::new(&mut buffer), &mut ())
190
            .unwrap();
191
        let decoded = ResponseRandomness::decode(&mut Decoder::new(&buffer), &mut ()).unwrap();
192
        assert_eq!(original, decoded);
193
    }
194

195
    #[property_test]
196
    fn announcement_cbor_roundtrip(original: Announcement) {
197
        let mut buffer = Vec::new();
198
        original
199
            .encode(&mut Encoder::new(&mut buffer), &mut ())
200
            .unwrap();
201
        let decoded = Announcement::decode(&mut Decoder::new(&buffer), &mut ()).unwrap();
202
        assert_eq!(original, decoded);
203
    }
204

205
    #[property_test]
206
    fn unit_vector_proof_cbor_roundtrip(original: UnitVectorProof) {
207
        let mut buffer = Vec::new();
208
        original
209
            .encode(&mut Encoder::new(&mut buffer), &mut ())
210
            .unwrap();
211
        let decoded = UnitVectorProof::decode(&mut Decoder::new(&buffer), &mut ()).unwrap();
212
        assert_eq!(original, decoded);
213
    }
214
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc