Skip to main content

dcrypt_sign/ecdsa/
common.rs

1//! Common utilities for ECDSA implementations
2
3use dcrypt_api::{error::Error as ApiError, Result as ApiResult};
4
5/// Return true when a canonical big-endian scalar lies above `order / 2`.
6///
7/// ECDSA admits both `(r, s)` and `(r, n - s)`. Requiring the lower half of
8/// the scalar range gives signatures a unique representation.
9pub(crate) fn is_high_s(s: &[u8], order: &[u8]) -> bool {
10    if s.len() != order.len() {
11        return true;
12    }
13    let mut carry = 0u8;
14    for (&scalar_byte, &order_byte) in s.iter().zip(order) {
15        let half_order_byte = (order_byte >> 1) | (carry << 7);
16        let next_carry = order_byte & 1;
17        if scalar_byte != half_order_byte {
18            return scalar_byte > half_order_byte;
19        }
20        carry = next_carry;
21    }
22    false
23}
24
25/// Return true only for a fixed-width, non-zero scalar strictly below the
26/// curve order. DER signature components are integers, not values to be
27/// silently reduced modulo the order.
28pub(crate) fn is_canonical_nonzero_scalar(value: &[u8], order: &[u8]) -> bool {
29    value.len() == order.len() && value.iter().any(|&byte| byte != 0) && value < order
30}
31
32/// ECDSA signature components (r, s)
33#[derive(Clone, Debug)]
34pub struct SignatureComponents {
35    pub r: Vec<u8>,
36    pub s: Vec<u8>,
37}
38
39impl SignatureComponents {
40    /// Serialize signature to DER format
41    pub fn to_der(&self) -> Vec<u8> {
42        // DER encoding: SEQUENCE { INTEGER r, INTEGER s }
43        let mut der = Vec::new();
44
45        // Add SEQUENCE tag
46        der.push(0x30);
47
48        let r_bytes = self.encode_integer(&self.r);
49        let s_bytes = self.encode_integer(&self.s);
50
51        let mut sequence = Vec::with_capacity(2 + r_bytes.len() + 2 + s_bytes.len());
52        sequence.push(0x02); // INTEGER tag
53        Self::encode_length(&mut sequence, r_bytes.len());
54        sequence.extend_from_slice(&r_bytes);
55
56        sequence.push(0x02); // INTEGER tag
57        Self::encode_length(&mut sequence, s_bytes.len());
58        sequence.extend_from_slice(&s_bytes);
59
60        Self::encode_length(&mut der, sequence.len());
61        der.extend_from_slice(&sequence);
62        der
63    }
64
65    /// Parse signature from DER format
66    pub fn from_der(der: &[u8]) -> ApiResult<Self> {
67        if der.len() < 2 {
68            return Err(ApiError::InvalidSignature {
69                context: "ECDSA DER parsing",
70                #[cfg(feature = "std")]
71                message: "DER signature too short".to_string(),
72            });
73        }
74
75        // Check SEQUENCE tag
76        if der[0] != 0x30 {
77            return Err(ApiError::InvalidSignature {
78                context: "ECDSA DER parsing",
79                #[cfg(feature = "std")]
80                message: "Invalid DER SEQUENCE tag".to_string(),
81            });
82        }
83
84        let (seq_len, mut pos) = Self::parse_length(der, 1)?;
85        let seq_end = pos
86            .checked_add(seq_len)
87            .ok_or_else(|| ApiError::InvalidSignature {
88                context: "ECDSA DER parsing",
89                #[cfg(feature = "std")]
90                message: "DER sequence length overflow".to_string(),
91            })?;
92
93        if seq_end != der.len() {
94            return Err(ApiError::InvalidSignature {
95                context: "ECDSA DER parsing",
96                #[cfg(feature = "std")]
97                message: "DER sequence length mismatch".to_string(),
98            });
99        }
100
101        let r = Self::parse_integer(der, &mut pos, "r")?;
102        let s = Self::parse_integer(der, &mut pos, "s")?;
103
104        if pos != seq_end {
105            return Err(ApiError::InvalidSignature {
106                context: "ECDSA DER parsing",
107                #[cfg(feature = "std")]
108                message: "Trailing data after ECDSA signature".to_string(),
109            });
110        }
111
112        Ok(SignatureComponents {
113            r: Self::decode_integer(&r),
114            s: Self::decode_integer(&s),
115        })
116    }
117
118    /// Encode integer for DER (add leading zero if high bit set)
119    fn encode_integer(&self, bytes: &[u8]) -> Vec<u8> {
120        let mut start = 0usize;
121        while start + 1 < bytes.len() && bytes[start] == 0x00 {
122            start += 1;
123        }
124
125        let trimmed = if bytes.is_empty() {
126            &[0x00][..]
127        } else {
128            &bytes[start..]
129        };
130
131        if trimmed[0] & 0x80 == 0 {
132            trimmed.to_vec()
133        } else {
134            let mut result = vec![0x00];
135            result.extend_from_slice(trimmed);
136            result
137        }
138    }
139
140    /// Decode integer from DER (remove leading zeros)
141    fn decode_integer(bytes: &[u8]) -> Vec<u8> {
142        let mut result = bytes.to_vec();
143        while result.len() > 1 && result[0] == 0x00 {
144            result.remove(0);
145        }
146        result
147    }
148
149    fn encode_length(out: &mut Vec<u8>, len: usize) {
150        if len < 0x80 {
151            out.push(len as u8);
152            return;
153        }
154
155        let mut buf = [0u8; core::mem::size_of::<usize>()];
156        let mut written = 0usize;
157        let mut value = len;
158
159        while value > 0 {
160            buf[buf.len() - 1 - written] = (value & 0xFF) as u8;
161            value >>= 8;
162            written += 1;
163        }
164
165        out.push(0x80 | written as u8);
166        out.extend_from_slice(&buf[buf.len() - written..]);
167    }
168
169    fn parse_length(der: &[u8], pos: usize) -> ApiResult<(usize, usize)> {
170        let first = *der.get(pos).ok_or_else(|| ApiError::InvalidSignature {
171            context: "ECDSA DER parsing",
172            #[cfg(feature = "std")]
173            message: "Missing DER length".to_string(),
174        })?;
175
176        if first & 0x80 == 0 {
177            return Ok((first as usize, pos + 1));
178        }
179
180        let num_len_bytes = (first & 0x7F) as usize;
181        if num_len_bytes == 0 {
182            return Err(ApiError::InvalidSignature {
183                context: "ECDSA DER parsing",
184                #[cfg(feature = "std")]
185                message: "Indefinite DER lengths are not allowed".to_string(),
186            });
187        }
188        if num_len_bytes > core::mem::size_of::<usize>() {
189            return Err(ApiError::InvalidSignature {
190                context: "ECDSA DER parsing",
191                #[cfg(feature = "std")]
192                message: "DER length is too large".to_string(),
193            });
194        }
195
196        let len_end = pos + 1 + num_len_bytes;
197        let len_bytes = der
198            .get(pos + 1..len_end)
199            .ok_or_else(|| ApiError::InvalidSignature {
200                context: "ECDSA DER parsing",
201                #[cfg(feature = "std")]
202                message: "Truncated DER length".to_string(),
203            })?;
204
205        if len_bytes.first() == Some(&0x00) {
206            return Err(ApiError::InvalidSignature {
207                context: "ECDSA DER parsing",
208                #[cfg(feature = "std")]
209                message: "DER length must use minimal encoding".to_string(),
210            });
211        }
212
213        let mut len = 0usize;
214        for &byte in len_bytes {
215            len = len
216                .checked_shl(8)
217                .ok_or_else(|| ApiError::InvalidSignature {
218                    context: "ECDSA DER parsing",
219                    #[cfg(feature = "std")]
220                    message: "DER length overflow".to_string(),
221                })?;
222            len |= byte as usize;
223        }
224
225        if len < 0x80 {
226            return Err(ApiError::InvalidSignature {
227                context: "ECDSA DER parsing",
228                #[cfg(feature = "std")]
229                message: "DER length must use short form".to_string(),
230            });
231        }
232
233        Ok((len, len_end))
234    }
235
236    fn parse_integer(der: &[u8], pos: &mut usize, name: &'static str) -> ApiResult<Vec<u8>> {
237        let tag = *der.get(*pos).ok_or_else(|| ApiError::InvalidSignature {
238            context: "ECDSA DER parsing",
239            #[cfg(feature = "std")]
240            message: format!("Missing DER INTEGER tag for {name}"),
241        })?;
242        if tag != 0x02 {
243            return Err(ApiError::InvalidSignature {
244                context: "ECDSA DER parsing",
245                #[cfg(feature = "std")]
246                message: format!("Invalid DER INTEGER tag for {name}"),
247            });
248        }
249        *pos += 1;
250
251        let (len, next_pos) = Self::parse_length(der, *pos)?;
252        *pos = next_pos;
253        if len == 0 {
254            return Err(ApiError::InvalidSignature {
255                context: "ECDSA DER parsing",
256                #[cfg(feature = "std")]
257                message: format!("DER INTEGER {name} cannot be empty"),
258            });
259        }
260
261        let int_end = pos
262            .checked_add(len)
263            .ok_or_else(|| ApiError::InvalidSignature {
264                context: "ECDSA DER parsing",
265                #[cfg(feature = "std")]
266                message: format!("DER INTEGER {name} length overflow"),
267            })?;
268
269        let value = der
270            .get(*pos..int_end)
271            .ok_or_else(|| ApiError::InvalidSignature {
272                context: "ECDSA DER parsing",
273                #[cfg(feature = "std")]
274                message: format!("Truncated DER INTEGER {name}"),
275            })?;
276
277        // ECDSA components are non-negative ASN.1 INTEGERs. Interpreting a
278        // negative two's-complement INTEGER as an unsigned magnitude creates
279        // multiple accepted encodings for the same mathematical value.
280        if value[0] & 0x80 != 0 {
281            return Err(ApiError::InvalidSignature {
282                context: "ECDSA DER parsing",
283                #[cfg(feature = "std")]
284                message: format!("DER INTEGER {name} must not be negative"),
285            });
286        }
287
288        if value.len() > 1 && value[0] == 0x00 && value[1] & 0x80 == 0 {
289            return Err(ApiError::InvalidSignature {
290                context: "ECDSA DER parsing",
291                #[cfg(feature = "std")]
292                message: format!("DER INTEGER {name} is not minimally encoded"),
293            });
294        }
295
296        *pos = int_end;
297        Ok(value.to_vec())
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304
305    #[test]
306    fn canonical_scalar_check_rejects_zero_order_and_larger_values() {
307        let order = [0x01, 0x00];
308        assert!(is_canonical_nonzero_scalar(&[0x00, 0xff], &order));
309        assert!(!is_canonical_nonzero_scalar(&[0x00, 0x00], &order));
310        assert!(!is_canonical_nonzero_scalar(&order, &order));
311        assert!(!is_canonical_nonzero_scalar(&[0x01, 0x01], &order));
312        assert!(!is_canonical_nonzero_scalar(&[0xff], &order));
313    }
314
315    #[test]
316    fn test_der_encoding() {
317        let sig = SignatureComponents {
318            r: vec![0x01, 0x23, 0x45, 0x67],
319            s: vec![0x89, 0xAB, 0xCD, 0xEF],
320        };
321
322        let der = sig.to_der();
323        let parsed = SignatureComponents::from_der(&der).unwrap();
324
325        assert_eq!(sig.r, parsed.r);
326        assert_eq!(sig.s, parsed.s);
327    }
328
329    #[test]
330    fn test_der_with_high_bit() {
331        // Test encoding when high bit is set (requires leading zero)
332        let sig = SignatureComponents {
333            r: vec![0xFF, 0x23, 0x45, 0x67],
334            s: vec![0x79, 0xAB, 0xCD, 0xEF],
335        };
336
337        let der = sig.to_der();
338
339        // Check that r has leading zero in DER
340        assert_eq!(der[3], 5); // r length should be 5 (extra zero byte)
341        assert_eq!(der[4], 0x00); // leading zero
342        assert_eq!(der[5], 0xFF); // original first byte
343
344        // Parse back and verify
345        let parsed = SignatureComponents::from_der(&der).unwrap();
346        assert_eq!(sig.r, parsed.r);
347        assert_eq!(sig.s, parsed.s);
348    }
349
350    #[test]
351    fn test_der_long_form_sequence_length_roundtrip() {
352        let sig = SignatureComponents {
353            r: vec![0x7F; 66],
354            s: vec![0x80; 66],
355        };
356
357        let der = sig.to_der();
358        assert_eq!(der[0], 0x30);
359        assert_eq!(der[1], 0x81);
360
361        let parsed = SignatureComponents::from_der(&der).unwrap();
362        assert_eq!(sig.r, parsed.r);
363        assert_eq!(sig.s, parsed.s);
364    }
365
366    #[test]
367    fn test_der_rejects_truncated_lengths_without_panicking() {
368        let malformed = [0x30, 0x06, 0x02, 0x02, 0x01];
369        assert!(SignatureComponents::from_der(&malformed).is_err());
370    }
371
372    #[test]
373    fn test_der_rejects_trailing_bytes() {
374        let der = [0x30, 0x08, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00];
375        assert!(SignatureComponents::from_der(&der).is_err());
376    }
377
378    #[test]
379    fn test_der_rejects_negative_integers() {
380        let negative_r = [0x30, 0x06, 0x02, 0x01, 0x80, 0x02, 0x01, 0x01];
381        let negative_s = [0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0xff];
382        assert!(SignatureComponents::from_der(&negative_r).is_err());
383        assert!(SignatureComponents::from_der(&negative_s).is_err());
384    }
385}