p256/
scalar.rs

1use alloc::vec::Vec;
2use core::{cmp::Ordering, ops::ShrAssign};
3
4use elliptic_curve::{
5    bigint::{ArrayEncoding, Encoding, U256},
6    ops::{Invert, Reduce},
7    rand_core::RngCore,
8    scalar::{FromUintUnchecked, IsHigh},
9    subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
10    zeroize::DefaultIsZeroes,
11    Field, PrimeField, ScalarPrimitive,
12};
13use hex_literal::hex;
14use openvm_algebra_guest::IntMod;
15
16use crate::{internal::P256Scalar, point::FieldBytes, NistP256, ORDER_HEX};
17
18impl P256Scalar {
19    /// Returns the SEC1 encoding of this scalar.
20    pub fn to_bytes(&self) -> FieldBytes {
21        self.to_be_bytes().into()
22    }
23}
24// --- Implement elliptic_curve traits on P256Scalar ---
25
26impl Copy for P256Scalar {}
27
28impl From<u64> for P256Scalar {
29    fn from(value: u64) -> Self {
30        Self::from_u64(value)
31    }
32}
33
34impl Default for P256Scalar {
35    fn default() -> Self {
36        <Self as IntMod>::ZERO
37    }
38}
39
40impl ConstantTimeEq for P256Scalar {
41    /// Compares raw little-endian byte representations for equality.
42    ///
43    /// **Note:** both operands must be in canonical (reduced) form.
44    /// Values created via `from_le_bytes_unchecked` or `from_be_bytes_unchecked`
45    /// must be reduced before calling `ct_eq`; otherwise two representations of
46    /// the same field element may compare as unequal.
47    fn ct_eq(&self, other: &Self) -> Choice {
48        self.as_le_bytes().ct_eq(other.as_le_bytes())
49    }
50}
51
52impl ConditionallySelectable for P256Scalar {
53    fn conditional_select(a: &P256Scalar, b: &P256Scalar, choice: Choice) -> P256Scalar {
54        P256Scalar::from_le_bytes_unchecked(
55            &a.as_le_bytes()
56                .iter()
57                .zip(b.as_le_bytes().iter())
58                .map(|(a, b)| u8::conditional_select(a, b, choice))
59                .collect::<Vec<_>>(),
60        )
61    }
62}
63
64impl Field for P256Scalar {
65    const ZERO: Self = <Self as IntMod>::ZERO;
66    const ONE: Self = <Self as IntMod>::ONE;
67
68    fn random(mut _rng: impl RngCore) -> Self {
69        unimplemented!()
70    }
71
72    fn square(&self) -> Self {
73        self * self
74    }
75
76    fn double(&self) -> Self {
77        self + self
78    }
79
80    fn invert(&self) -> CtOption<Self> {
81        // needs to be in canonical form for ct_eq
82        self.assert_reduced();
83        let is_zero = self.ct_eq(&<Self as IntMod>::ZERO);
84        CtOption::new(
85            <P256Scalar as openvm_algebra_guest::Field>::invert(self),
86            !is_zero,
87        )
88    }
89
90    #[allow(clippy::many_single_char_names)]
91    fn sqrt(&self) -> CtOption<Self> {
92        match <Self as openvm_algebra_guest::Sqrt>::sqrt(self) {
93            Some(sqrt) => CtOption::new(sqrt, 1.into()),
94            None => CtOption::new(<Self as Field>::ZERO, 0.into()),
95        }
96    }
97
98    fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
99        ff::helpers::sqrt_ratio_generic(num, div)
100    }
101}
102
103const fn seven_le() -> [u8; 32] {
104    let mut buf = [0u8; 32];
105    buf[0] = 7;
106    buf
107}
108
109impl PrimeField for P256Scalar {
110    type Repr = FieldBytes;
111
112    const MODULUS: &'static str = ORDER_HEX;
113    const NUM_BITS: u32 = 256;
114    const CAPACITY: u32 = 255;
115    const TWO_INV: Self = Self::from_const_bytes(hex!(
116        "a992317e61e5dc7942cf8bd3567d73deffffffffffffff7f00000080ffffff7f"
117    ));
118    const MULTIPLICATIVE_GENERATOR: Self = Self::from_const_bytes(seven_le());
119    const S: u32 = 4;
120    const ROOT_OF_UNITY: Self = Self::from_const_bytes(hex!(
121        "02661eb4fbd79205af8d3704d0ca4615fc3d2a84ce7a80ba9209772a067fc9ff"
122    ));
123    const ROOT_OF_UNITY_INV: Self = Self::from_const_bytes(hex!(
124        "6437c757067f9c3737414c797c11ace3ae1c135804fa45c62a6fd462556aa6a0"
125    ));
126    const DELTA: Self = Self::from_const_bytes(hex!(
127        "817d05a5391e0000000000000000000000000000000000000000000000000000"
128    ));
129
130    /// Attempts to parse the given byte array as an SEC1-encoded scalar.
131    ///
132    /// Returns None if the byte array does not contain a big-endian integer in the range
133    /// [0, p).
134    fn from_repr(bytes: FieldBytes) -> CtOption<Self> {
135        let ret = Self::from_be_bytes_unchecked(bytes.as_slice());
136        CtOption::new(ret, (ret.is_reduced() as u8).into())
137    }
138
139    // Endianness should match from_repr
140    fn to_repr(&self) -> FieldBytes {
141        *FieldBytes::from_slice(&self.to_be_bytes())
142    }
143
144    fn is_odd(&self) -> Choice {
145        (self.as_le_bytes()[0] & 1).into()
146    }
147}
148
149impl ShrAssign<usize> for P256Scalar {
150    fn shr_assign(&mut self, _rhs: usize) {
151        // I don't think this is used anywhere
152        unimplemented!()
153    }
154}
155
156impl Reduce<U256> for P256Scalar {
157    type Bytes = FieldBytes;
158
159    fn reduce(w: U256) -> Self {
160        <Self as openvm_algebra_guest::Reduce>::reduce_le_bytes(&w.to_le_bytes())
161    }
162
163    #[inline]
164    fn reduce_bytes(bytes: &FieldBytes) -> Self {
165        Self::reduce(U256::from_be_byte_array(*bytes))
166    }
167}
168
169impl PartialOrd for P256Scalar {
170    // requires self and other to be in canonical form
171    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
172        self.assert_reduced();
173        other.assert_reduced();
174        Some(
175            self.to_be_bytes()
176                .iter()
177                .zip(other.to_be_bytes().iter())
178                .map(|(a, b)| a.cmp(b))
179                .find(|ord| *ord != Ordering::Equal)
180                .unwrap_or(Ordering::Equal),
181        )
182    }
183}
184
185impl IsHigh for P256Scalar {
186    fn is_high(&self) -> Choice {
187        // self > n/2
188        // iff self + self overflows
189        // iff self + self < self
190        ((self + self < *self) as u8).into()
191    }
192}
193
194impl Invert for P256Scalar {
195    type Output = CtOption<Self>;
196
197    fn invert(&self) -> CtOption<Self> {
198        <Self as Field>::invert(self)
199    }
200}
201
202impl FromUintUnchecked for P256Scalar {
203    type Uint = U256;
204
205    fn from_uint_unchecked(uint: Self::Uint) -> Self {
206        Self::from_le_bytes_unchecked(&uint.to_le_bytes())
207    }
208}
209
210impl From<ScalarPrimitive<NistP256>> for P256Scalar {
211    fn from(scalar: ScalarPrimitive<NistP256>) -> Self {
212        Self::from_le_bytes_unchecked(&scalar.as_uint().to_le_bytes())
213    }
214}
215
216impl From<P256Scalar> for ScalarPrimitive<NistP256> {
217    fn from(scalar: P256Scalar) -> ScalarPrimitive<NistP256> {
218        ScalarPrimitive::from_slice(&scalar.to_be_bytes()).unwrap()
219    }
220}
221
222impl DefaultIsZeroes for P256Scalar {}
223
224impl AsRef<P256Scalar> for P256Scalar {
225    fn as_ref(&self) -> &P256Scalar {
226        self
227    }
228}
229
230impl From<P256Scalar> for U256 {
231    fn from(scalar: P256Scalar) -> Self {
232        U256::from_be_slice(&scalar.to_be_bytes())
233    }
234}
235
236impl From<P256Scalar> for FieldBytes {
237    fn from(scalar: P256Scalar) -> Self {
238        *FieldBytes::from_slice(&scalar.to_be_bytes())
239    }
240}