Skip to main content

threshold_pairing/
into_fr.rs

1use super::Scalar;
2use crate::error::{Error, Result};
3use ff::Field;
4
5/// A conversion into an element of the scalar field.
6pub trait IntoScalar: Copy {
7    /// Converts `self` to a field element.
8    fn into_scalar(self) -> Scalar;
9}
10
11impl IntoScalar for Scalar {
12    fn into_scalar(self) -> Scalar {
13        self
14    }
15}
16
17impl IntoScalar for u64 {
18    fn into_scalar(self) -> Scalar {
19        Scalar::from(self)
20    }
21}
22
23impl IntoScalar for usize {
24    fn into_scalar(self) -> Scalar {
25        (self as u64).into_scalar()
26    }
27}
28
29impl IntoScalar for i32 {
30    fn into_scalar(self) -> Scalar {
31        if self >= 0 {
32            (self as u64).into_scalar()
33        } else {
34            // Use wrapping_neg to correctly handle i32::MIN
35            // i32::MIN.wrapping_neg() as u64 gives the correct absolute value
36            let abs_val = (self as i64).wrapping_neg() as u64;
37            abs_val.into_scalar().neg()
38        }
39    }
40}
41
42impl IntoScalar for i64 {
43    fn into_scalar(self) -> Scalar {
44        if self >= 0 {
45            (self as u64).into_scalar()
46        } else {
47            // Use wrapping_neg to correctly handle i64::MIN
48            // i64::MIN.wrapping_neg() as u64 gives 2^63, the correct absolute value
49            let abs_val = (self as u64).wrapping_neg();
50            abs_val.into_scalar().neg()
51        }
52    }
53}
54
55impl<T: IntoScalar> IntoScalar for &T {
56    fn into_scalar(self) -> Scalar {
57        (*self).into_scalar()
58    }
59}
60
61/// A safe conversion into a *non-zero* scalar field element, suitable for use as a share index.
62///
63/// This trait is the required bound for all share-index parameters (e.g.
64/// [`SecretKeySet::secret_key_share`](crate::SecretKeySet::secret_key_share) and
65/// [`PublicKeySet::public_key_share`](crate::PublicKeySet::public_key_share)).
66///
67/// # Security
68///
69/// Share indices are mapped to scalar field elements via `index + 1`. If the resulting
70/// scalar is zero — which happens when a signed index value such as `-1i32` is used —
71/// the evaluation point equals `poly(0)`, i.e. the **master secret key**. This trait
72/// enforces that the post-mapping scalar is non-zero, preventing that attack.
73///
74/// For all unsigned types (`u64`, `usize`, `Scalar`) the check is statically guaranteed
75/// to succeed and the blanket implementation returns `Ok` without runtime overhead.
76pub trait TryIntoScalarSafe: Copy {
77    /// Converts `self` to a scalar, returning `Err(Error::IndexMapsToZero)` if the
78    /// conversion would produce the zero scalar after the `+ 1` offset applied
79    /// by the share-index machinery.
80    ///
81    /// The check is performed *after* adding 1, so the predicate is:
82    /// `self.into_scalar() + 1 != Scalar::zero()`.
83    fn try_into_scalar_safe(self) -> Result<Scalar>;
84}
85
86/// Blanket implementation for all types that already implement [`IntoScalar`].
87///
88/// For unsigned types the `+ 1` offset can never produce zero (since the only way
89/// to get zero is `x + 1 ≡ 0 (mod p)`, i.e. `x = p - 1`, which is representable
90/// only as a `Scalar` value, not as a `u64`/`usize`). We still perform the runtime
91/// check so that callers who provide a raw `Scalar` equal to `p - 1` are caught.
92impl<T: IntoScalar> TryIntoScalarSafe for T {
93    fn try_into_scalar_safe(self) -> Result<Scalar> {
94        let s = self.into_scalar();
95        // Compute index + 1 (what the share machinery will use as the evaluation point)
96        let eval_point = s + Scalar::one();
97        if bool::from(eval_point.is_zero()) {
98            return Err(Error::IndexMapsToZero);
99        }
100        Ok(s)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use core::ops::Neg;
108
109    #[test]
110    fn test_i32_min_does_not_overflow() {
111        let scalar = i32::MIN.into_scalar();
112        // i32::MIN is -2147483648, so the scalar should be -2147483648 mod p
113        // which equals (p - 2147483648)
114        let expected = (2147483648u64).into_scalar().neg();
115        assert_eq!(scalar, expected);
116    }
117
118    #[test]
119    fn test_i64_min_does_not_overflow() {
120        let scalar = i64::MIN.into_scalar();
121        // i64::MIN is -9223372036854775808, so the scalar should be the negation
122        // of 2^63 in the field
123        let expected = (9223372036854775808u64).into_scalar().neg();
124        assert_eq!(scalar, expected);
125    }
126
127    #[test]
128    fn test_negative_values_correct() {
129        // Test that negative values are correctly converted
130        let neg_one_i32 = (-1i32).into_scalar();
131        let neg_one_i64 = (-1i64).into_scalar();
132        let one = 1u64.into_scalar();
133
134        assert_eq!(neg_one_i32, one.neg());
135        assert_eq!(neg_one_i64, one.neg());
136    }
137
138    // --- TryIntoScalarSafe tests ---
139
140    #[test]
141    fn test_safe_negative_one_i32_rejected() {
142        // -1i32 maps to p-1, and (p-1) + 1 = p = 0 in the field => must be rejected
143        assert_eq!((-1i32).try_into_scalar_safe(), Err(Error::IndexMapsToZero));
144    }
145
146    #[test]
147    fn test_safe_negative_one_i64_rejected() {
148        assert_eq!((-1i64).try_into_scalar_safe(), Err(Error::IndexMapsToZero));
149    }
150
151    #[test]
152    fn test_safe_unsigned_zero_accepted() {
153        // 0u64 + 1 = 1 != 0, must be accepted
154        assert!(0u64.try_into_scalar_safe().is_ok());
155    }
156
157    #[test]
158    fn test_safe_usize_zero_accepted() {
159        assert!(0usize.try_into_scalar_safe().is_ok());
160    }
161
162    #[test]
163    fn test_safe_positive_i32_accepted() {
164        assert!(0i32.try_into_scalar_safe().is_ok());
165        assert!(1i32.try_into_scalar_safe().is_ok());
166        assert!(42i32.try_into_scalar_safe().is_ok());
167    }
168
169    #[test]
170    fn test_safe_large_negative_i32_accepted() {
171        // -2i32 maps to p-2, and (p-2) + 1 = p-1 != 0 => accepted
172        assert!((-2i32).try_into_scalar_safe().is_ok());
173    }
174
175    #[test]
176    fn test_safe_scalar_p_minus_1_rejected() {
177        // Scalar equal to p-1 should also be rejected since (p-1)+1 = 0
178        let p_minus_1 = Scalar::zero() - Scalar::one();
179        assert_eq!(
180            p_minus_1.try_into_scalar_safe(),
181            Err(Error::IndexMapsToZero)
182        );
183    }
184}