openvm_ecc_guest/
ecdsa.rs

1use alloc::vec::Vec;
2use core::ops::{Add, Mul};
3
4use ecdsa_core::{
5    self,
6    hazmat::{bits2field, DigestPrimitive},
7    signature::{
8        digest::{Digest, FixedOutput},
9        hazmat::PrehashVerifier,
10        DigestVerifier, Verifier,
11    },
12    EncodedPoint, Error, RecoveryId, Result, Signature, SignatureSize,
13};
14use elliptic_curve::{
15    bigint::CheckedAdd,
16    generic_array::{typenum::Unsigned, ArrayLength},
17    sec1::{FromEncodedPoint, ModulusSize, Tag, ToEncodedPoint},
18    subtle::{Choice, ConditionallySelectable, CtOption},
19    CurveArithmetic, FieldBytes, FieldBytesEncoding, FieldBytesSize, PrimeCurve,
20};
21use openvm_algebra_guest::{DivUnsafe, IntMod, Reduce};
22
23use crate::{
24    weierstrass::{FromCompressed, IntrinsicCurve, WeierstrassPoint},
25    CyclicGroup, Group,
26};
27
28type Coordinate<C> = <<C as IntrinsicCurve>::Point as WeierstrassPoint>::Coordinate;
29type Scalar<C> = <C as IntrinsicCurve>::Scalar;
30type AffinePoint<C> = <C as IntrinsicCurve>::Point;
31
32//
33// Signing implementations are placeholders to support patching compilation
34//
35
36/// This is placeholder struct for compatibility purposes with the `ecdsa` crate.
37/// Signing from private keys is not supported yet.
38#[derive(Clone)]
39pub struct SigningKey<C: IntrinsicCurve> {
40    /// ECDSA signing keys are non-zero elements of a given curve's scalar field.
41    #[allow(dead_code)]
42    secret_scalar: NonZeroScalar<C>,
43
44    /// Verifying key which corresponds to this signing key.
45    verifying_key: VerifyingKey<C>,
46}
47
48#[allow(dead_code)]
49#[derive(Clone)]
50pub struct NonZeroScalar<C: IntrinsicCurve> {
51    scalar: Scalar<C>,
52}
53
54impl<C: IntrinsicCurve> SigningKey<C> {
55    pub fn from_slice(_bytes: &[u8]) -> Result<Self> {
56        todo!("signing is not yet implemented")
57    }
58
59    pub fn verifying_key(&self) -> &VerifyingKey<C> {
60        &self.verifying_key
61    }
62}
63
64impl<C> SigningKey<C>
65where
66    C: IntrinsicCurve + PrimeCurve,
67{
68    pub fn sign_prehash_recoverable(&self, _prehash: &[u8]) -> Result<(Signature<C>, RecoveryId)> {
69        todo!("signing is not yet implemented")
70    }
71}
72
73// This struct is public because it is used by the VerifyPrimitive impl in the k256 and p256 guest
74// libraries.
75#[repr(C)]
76#[derive(Clone)]
77pub struct VerifyingKey<C: IntrinsicCurve> {
78    pub(crate) inner: PublicKey<C>,
79}
80
81// This struct is public because it is used by the VerifyPrimitive impl in the k256 and p256 guest
82#[repr(C)]
83#[derive(Clone)]
84pub struct PublicKey<C: IntrinsicCurve> {
85    /// Affine point
86    point: AffinePoint<C>,
87}
88
89impl<C: IntrinsicCurve> PublicKey<C>
90where
91    C::Point: WeierstrassPoint + Group + FromCompressed<Coordinate<C>>,
92    Coordinate<C>: IntMod,
93{
94    /// Convert an [`AffinePoint`] into a [`PublicKey`].
95    /// In addition, for `Coordinate<C>` implementing `IntMod`, this function will assert that the
96    /// affine coordinates of `point` are both in canonical form.
97    pub fn from_affine(point: AffinePoint<C>) -> Result<Self> {
98        // Internally this calls `is_eq` on `x` and `y` coordinates, which will assert `x, y` are
99        // reduced.
100        if point.is_identity() {
101            Err(Error::new())
102        } else {
103            Ok(Self { point })
104        }
105    }
106
107    /// # Safety
108    /// - The uncompressed deserialization checks that the point is on the curve but does not
109    ///   perform any additional subgroup checks.
110    pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self>
111    where
112        for<'a> &'a Coordinate<C>: Mul<&'a Coordinate<C>, Output = Coordinate<C>>,
113    {
114        if bytes.is_empty() {
115            return Err(Error::new());
116        }
117
118        // Validate tag
119        let tag = Tag::from_u8(bytes[0]).map_err(|_| Error::new())?;
120
121        // Validate length
122        let expected_len = tag.message_len(Coordinate::<C>::NUM_LIMBS);
123        if bytes.len() != expected_len {
124            return Err(Error::new());
125        }
126
127        match tag {
128            Tag::Identity => {
129                // Reject identity point, a PublicKey must be non-identity
130                Err(Error::new())
131            }
132
133            Tag::CompressedEvenY | Tag::CompressedOddY => {
134                let x = Coordinate::<C>::from_be_bytes(&bytes[1..]).ok_or_else(Error::new)?;
135                let rec_id = bytes[0] & 1;
136                let point = FromCompressed::decompress(x, &rec_id).ok_or_else(Error::new)?;
137                // Decompressed point will never be identity
138                Ok(Self { point })
139            }
140
141            Tag::Uncompressed => {
142                let (x_bytes, y_bytes) = bytes[1..].split_at(Coordinate::<C>::NUM_LIMBS);
143                let x = Coordinate::<C>::from_be_bytes(x_bytes).ok_or_else(Error::new)?;
144                let y = Coordinate::<C>::from_be_bytes(y_bytes).ok_or_else(Error::new)?;
145                let point =
146                    unsafe { <C as IntrinsicCurve>::Point::from_xy(x, y).ok_or_else(Error::new)? };
147                Self::from_affine(point)
148            }
149
150            _ => Err(Error::new()),
151        }
152    }
153
154    pub fn to_sec1_bytes(&self, compress: bool) -> Vec<u8> {
155        if self.point.is_identity() {
156            return vec![0x00];
157        }
158
159        let (x, y) = self.point.clone().into_coords();
160
161        if compress {
162            let mut bytes = Vec::<u8>::with_capacity(1 + Coordinate::<C>::NUM_LIMBS);
163            let tag = if y.as_le_bytes()[0] & 1 == 1 {
164                Tag::CompressedOddY
165            } else {
166                Tag::CompressedEvenY
167            };
168            bytes.push(tag.into());
169            bytes.extend_from_slice(x.to_be_bytes().as_ref());
170            bytes
171        } else {
172            let mut bytes = Vec::<u8>::with_capacity(1 + Coordinate::<C>::NUM_LIMBS * 2);
173            bytes.push(Tag::Uncompressed.into());
174            bytes.extend_from_slice(x.to_be_bytes().as_ref());
175            bytes.extend_from_slice(y.to_be_bytes().as_ref());
176            bytes
177        }
178    }
179
180    pub fn as_affine(&self) -> &AffinePoint<C> {
181        &self.point
182    }
183
184    pub fn into_affine(self) -> AffinePoint<C> {
185        self.point
186    }
187}
188
189impl<C: IntrinsicCurve> VerifyingKey<C>
190where
191    C::Point: WeierstrassPoint + Group + FromCompressed<Coordinate<C>>,
192    Coordinate<C>: IntMod,
193    for<'a> &'a Coordinate<C>: Mul<&'a Coordinate<C>, Output = Coordinate<C>>,
194{
195    pub fn new(public_key: PublicKey<C>) -> Self {
196        Self { inner: public_key }
197    }
198
199    pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> {
200        let public_key = PublicKey::<C>::from_sec1_bytes(bytes)?;
201        Ok(Self::new(public_key))
202    }
203
204    pub fn from_affine(point: <C as IntrinsicCurve>::Point) -> Result<Self> {
205        let public_key = PublicKey::<C>::from_affine(point)?;
206        Ok(Self::new(public_key))
207    }
208
209    pub fn to_sec1_bytes(&self, compress: bool) -> Vec<u8> {
210        self.inner.to_sec1_bytes(compress)
211    }
212
213    pub fn as_affine(&self) -> &<C as IntrinsicCurve>::Point {
214        self.inner.as_affine()
215    }
216
217    pub fn into_affine(self) -> <C as IntrinsicCurve>::Point {
218        self.inner.into_affine()
219    }
220}
221
222// Functions for compatibility with `ecdsa` crate
223impl<C> VerifyingKey<C>
224where
225    C: IntrinsicCurve + PrimeCurve,
226    C::Point: WeierstrassPoint + CyclicGroup + FromCompressed<Coordinate<C>> + VerifyCustomHook<C>,
227    Coordinate<C>: IntMod,
228    C::Scalar: IntMod + Reduce,
229    for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>,
230    for<'a> &'a Coordinate<C>: Mul<&'a Coordinate<C>, Output = Coordinate<C>>,
231    FieldBytesSize<C>: ModulusSize,
232    SignatureSize<C>: ArrayLength<u8>,
233{
234    /// Recover a [`VerifyingKey`] from the given message, signature, and
235    /// [`RecoveryId`].
236    ///
237    /// The message is first hashed using this curve's [`DigestPrimitive`].
238    pub fn recover_from_msg(
239        msg: &[u8],
240        signature: &Signature<C>,
241        recovery_id: RecoveryId,
242    ) -> Result<Self>
243    where
244        C: DigestPrimitive,
245    {
246        Self::recover_from_digest(C::Digest::new_with_prefix(msg), signature, recovery_id)
247    }
248
249    /// Recover a [`VerifyingKey`] from the given message [`Digest`],
250    /// signature, and [`RecoveryId`].
251    pub fn recover_from_digest<D>(
252        msg_digest: D,
253        signature: &Signature<C>,
254        recovery_id: RecoveryId,
255    ) -> Result<Self>
256    where
257        D: Digest,
258    {
259        Self::recover_from_prehash(&msg_digest.finalize(), signature, recovery_id)
260    }
261
262    /// Recover a [`VerifyingKey`] from the given `prehash` of a message, the
263    /// signature over that prehashed message, and a [`RecoveryId`].
264    /// Note that this function does not verify the signature with the recovered key.
265    pub fn recover_from_prehash(
266        prehash: &[u8],
267        signature: &Signature<C>,
268        recovery_id: RecoveryId,
269    ) -> Result<Self> {
270        let sig = signature.to_bytes();
271        let vk = Self::recover_from_prehash_noverify(prehash, &sig, recovery_id)?;
272        vk.inner.as_affine().verify_hook(prehash, signature)?;
273        Ok(vk)
274    }
275}
276
277/// To match the RustCrypto trait [VerifyPrimitive]. Certain curves have special verification logic
278/// outside of the general ECDSA verification algorithm. This trait provides a hook for such logic.
279///
280/// This trait is intended to be implemented on type which can access
281/// the affine point representing the public key via `&self`, such as a
282/// particular curve's `AffinePoint` type.
283pub trait VerifyCustomHook<C>: WeierstrassPoint
284where
285    C: IntrinsicCurve + PrimeCurve,
286    SignatureSize<C>: ArrayLength<u8>,
287{
288    /// This is **NOT** the full ECDSA signature verification algorithm. The implementer should only
289    /// add additional verification logic not contained in [verify_prehashed]. The default
290    /// implementation does nothing.
291    ///
292    /// Accepts the following arguments:
293    ///
294    /// - `z`: message digest to be verified. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST
295    ///   ALGORITHM!!!
296    /// - `sig`: signature to be verified against the key and message
297    fn verify_hook(&self, _z: &[u8], _sig: &Signature<C>) -> Result<()> {
298        Ok(())
299    }
300}
301
302//
303// `*Verifier` trait impls
304//
305
306impl<C, D> DigestVerifier<D, Signature<C>> for VerifyingKey<C>
307where
308    C: PrimeCurve + IntrinsicCurve,
309    D: Digest + FixedOutput<OutputSize = FieldBytesSize<C>>,
310    SignatureSize<C>: ArrayLength<u8>,
311    C::Point: WeierstrassPoint + CyclicGroup + FromCompressed<Coordinate<C>> + VerifyCustomHook<C>,
312    Coordinate<C>: IntMod,
313    <C as IntrinsicCurve>::Scalar: IntMod + Reduce,
314    for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>,
315    for<'a> &'a Scalar<C>: DivUnsafe<&'a Scalar<C>, Output = Scalar<C>>,
316{
317    fn verify_digest(&self, msg_digest: D, signature: &Signature<C>) -> Result<()> {
318        PrehashVerifier::<Signature<C>>::verify_prehash(
319            self,
320            &msg_digest.finalize_fixed(),
321            signature,
322        )
323    }
324}
325
326impl<C> PrehashVerifier<Signature<C>> for VerifyingKey<C>
327where
328    C: PrimeCurve + IntrinsicCurve,
329    SignatureSize<C>: ArrayLength<u8>,
330    C::Point: WeierstrassPoint + CyclicGroup + FromCompressed<Coordinate<C>> + VerifyCustomHook<C>,
331    Coordinate<C>: IntMod,
332    C::Scalar: IntMod + Reduce,
333    for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>,
334    for<'a> &'a Scalar<C>: DivUnsafe<&'a Scalar<C>, Output = Scalar<C>>,
335{
336    fn verify_prehash(&self, prehash: &[u8], signature: &Signature<C>) -> Result<()> {
337        self.inner.as_affine().verify_hook(prehash, signature)?;
338        verify_prehashed::<C>(
339            self.inner.as_affine().clone(),
340            prehash,
341            &signature.to_bytes(),
342        )
343    }
344}
345
346impl<C> Verifier<Signature<C>> for VerifyingKey<C>
347where
348    C: PrimeCurve + CurveArithmetic + DigestPrimitive + IntrinsicCurve,
349    SignatureSize<C>: ArrayLength<u8>,
350    C::Point: WeierstrassPoint + CyclicGroup + FromCompressed<Coordinate<C>> + VerifyCustomHook<C>,
351    Coordinate<C>: IntMod,
352    <C as IntrinsicCurve>::Scalar: IntMod + Reduce,
353    for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>,
354    for<'a> &'a Scalar<C>: DivUnsafe<&'a Scalar<C>, Output = Scalar<C>>,
355{
356    fn verify(&self, msg: &[u8], signature: &Signature<C>) -> Result<()> {
357        self.verify_digest(C::Digest::new_with_prefix(msg), signature)
358    }
359}
360
361//
362// copied from `ecdsa`
363//
364impl<C> VerifyingKey<C>
365where
366    C: CurveArithmetic + IntrinsicCurve,
367    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C> + Default + ConditionallySelectable,
368    FieldBytesSize<C>: ModulusSize,
369{
370    /// Initialize [`VerifyingKey`] from an [`EncodedPoint`].
371    pub fn from_encoded_point(public_key: &EncodedPoint<C>) -> Result<Self> {
372        Option::from(PublicKey::<C>::from_encoded_point(public_key))
373            .map(|public_key| Self { inner: public_key })
374            .ok_or_else(Error::new)
375    }
376
377    /// Serialize this [`VerifyingKey`] as a SEC1 [`EncodedPoint`], optionally
378    /// applying point compression.
379    pub fn to_encoded_point(&self, compress: bool) -> EncodedPoint<C> {
380        self.inner.to_encoded_point(compress)
381    }
382}
383
384//
385// sec1 traits copied from elliptic_curve
386//
387impl<C> FromEncodedPoint<C> for PublicKey<C>
388where
389    C: CurveArithmetic + IntrinsicCurve,
390    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C> + Default + ConditionallySelectable,
391    FieldBytesSize<C>: ModulusSize,
392{
393    /// Initialize [`PublicKey`] from an [`EncodedPoint`]
394    fn from_encoded_point(encoded_point: &EncodedPoint<C>) -> CtOption<Self> {
395        AffinePoint::<C>::from_encoded_point(encoded_point).and_then(|point| {
396            // Defeating the point of `subtle`, but the use case is specifically a public key
397            let is_identity = Choice::from(u8::from(encoded_point.is_identity()));
398            CtOption::new(PublicKey { point }, !is_identity)
399        })
400    }
401}
402
403impl<C> ToEncodedPoint<C> for PublicKey<C>
404where
405    C: CurveArithmetic + IntrinsicCurve,
406    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
407    FieldBytesSize<C>: ModulusSize,
408{
409    /// Serialize this [`PublicKey`] as a SEC1 [`EncodedPoint`], optionally applying
410    /// point compression
411    fn to_encoded_point(&self, compress: bool) -> EncodedPoint<C> {
412        self.point.to_encoded_point(compress)
413    }
414}
415
416// Custom openvm implementations
417impl<C> VerifyingKey<C>
418where
419    C: IntrinsicCurve + PrimeCurve,
420    C::Point: WeierstrassPoint + CyclicGroup + FromCompressed<Coordinate<C>>,
421    Coordinate<C>: IntMod,
422    C::Scalar: IntMod + Reduce,
423{
424    /// ## Assumption
425    /// To use this implementation, the `Signature<C>`, `Coordinate<C>`, and `FieldBytes<C>` should
426    /// all be encoded in big endian bytes. The implementation also assumes that
427    /// `Scalar::<C>::NUM_LIMBS <= FieldBytesSize::<C>::USIZE <= Coordinate::<C>::NUM_LIMBS`.
428    ///
429    /// Ref: <https://github.com/RustCrypto/signatures/blob/85c984bcc9927c2ce70c7e15cbfe9c6936dd3521/ecdsa/src/recovery.rs#L297>
430    ///
431    /// Recovery does not require additional signature verification: <https://github.com/RustCrypto/signatures/pull/831>
432    #[allow(non_snake_case)]
433    pub fn recover_from_prehash_noverify(
434        prehash: &[u8],
435        sig: &[u8],
436        recovery_id: RecoveryId,
437    ) -> Result<Self>
438    where
439        for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>,
440        for<'a> &'a Coordinate<C>: Mul<&'a Coordinate<C>, Output = Coordinate<C>>,
441    {
442        // This should get compiled out:
443        assert!(Scalar::<C>::NUM_LIMBS <= Coordinate::<C>::NUM_LIMBS);
444        // IntMod limbs are currently always bytes
445        assert_eq!(sig.len(), <C as IntrinsicCurve>::Scalar::NUM_LIMBS * 2);
446        // Signature is default encoded in big endian bytes
447        let (r_be, s_be) = sig.split_at(<C as IntrinsicCurve>::Scalar::NUM_LIMBS);
448        // Note: Scalar internally stores using little endian
449        let r = Scalar::<C>::from_be_bytes(r_be).ok_or_else(Error::new)?;
450        let s = Scalar::<C>::from_be_bytes(s_be).ok_or_else(Error::new)?;
451        if r == Scalar::<C>::ZERO || s == Scalar::<C>::ZERO {
452            return Err(Error::new());
453        }
454
455        // Perf: don't use bits2field from ::ecdsa
456        let prehash_bytes = bits2field::<C>(prehash)?;
457        // If prehash is longer than Scalar::NUM_LIMBS, take leftmost bytes
458        let trim = prehash_bytes.len().saturating_sub(Scalar::<C>::NUM_LIMBS);
459        // from_be_bytes still works if len < Scalar::NUM_LIMBS
460        // we don't need to reduce because IntMod is up to modular equivalence
461        let z = Scalar::<C>::from_be_bytes_unchecked(&prehash_bytes[..prehash_bytes.len() - trim]);
462
463        // `r` is in the Scalar field, we now possibly add C::ORDER to it to get `x`
464        // in the Coordinate field.
465        // We take some extra care for the case when FieldBytesSize<C> may be larger than
466        // Scalar::<C>::NUM_LIMBS.
467        let mut r_bytes = {
468            let mut r_bytes = FieldBytes::<C>::default();
469            assert!(FieldBytesSize::<C>::USIZE >= Scalar::<C>::NUM_LIMBS);
470            let offset = r_bytes.len().saturating_sub(r_be.len());
471            r_bytes[offset..].copy_from_slice(r_be);
472            r_bytes
473        };
474        if recovery_id.is_x_reduced() {
475            match Option::<C::Uint>::from(
476                C::Uint::decode_field_bytes(&r_bytes).checked_add(&C::ORDER),
477            ) {
478                Some(restored) => r_bytes = restored.encode_field_bytes(),
479                // No reduction should happen here if r was reduced
480                None => {
481                    return Err(Error::new());
482                }
483            };
484        }
485        assert!(FieldBytesSize::<C>::USIZE <= Coordinate::<C>::NUM_LIMBS);
486        let x = Coordinate::<C>::from_be_bytes(&r_bytes).ok_or_else(Error::new)?;
487        let rec_id = recovery_id.to_byte();
488        // The point R decompressed from x-coordinate `r`
489        let R: C::Point = FromCompressed::decompress(x, &rec_id).ok_or_else(Error::new)?;
490
491        let neg_u1 = z.div_unsafe(&r);
492        let u2 = s.div_unsafe(&r);
493        let NEG_G = C::Point::NEG_GENERATOR;
494        let point = <C as IntrinsicCurve>::msm(&[neg_u1, u2], &[NEG_G, R]);
495        let vk = VerifyingKey::from_affine(point)?;
496
497        Ok(vk)
498    }
499}
500
501/// Assumes that `sig` is proper encoding of `r, s`.
502// Ref: https://docs.rs/ecdsa/latest/src/ecdsa/hazmat.rs.html#270
503#[allow(non_snake_case)]
504pub fn verify_prehashed<C>(pubkey: AffinePoint<C>, prehash: &[u8], sig: &[u8]) -> Result<()>
505where
506    C: IntrinsicCurve + PrimeCurve,
507    C::Point: WeierstrassPoint + CyclicGroup + FromCompressed<Coordinate<C>>,
508    Coordinate<C>: IntMod,
509    C::Scalar: IntMod + Reduce,
510    for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>,
511    for<'a> &'a Scalar<C>: DivUnsafe<&'a Scalar<C>, Output = Scalar<C>>,
512{
513    // This should get compiled out:
514    assert!(Scalar::<C>::NUM_LIMBS <= Coordinate::<C>::NUM_LIMBS);
515    // IntMod limbs are currently always bytes
516    assert_eq!(sig.len(), Scalar::<C>::NUM_LIMBS * 2);
517    // Signature is default encoded in big endian bytes
518    let (r_be, s_be) = sig.split_at(<C as IntrinsicCurve>::Scalar::NUM_LIMBS);
519    // Note: Scalar internally stores using little endian
520    let r = Scalar::<C>::from_be_bytes(r_be).ok_or_else(Error::new)?;
521    let s = Scalar::<C>::from_be_bytes(s_be).ok_or_else(Error::new)?;
522    if r == Scalar::<C>::ZERO || s == Scalar::<C>::ZERO {
523        return Err(Error::new());
524    }
525
526    // Perf: don't use bits2field from ::ecdsa
527    let prehash_bytes = bits2field::<C>(prehash)?;
528    // If prehash is longer than Scalar::NUM_LIMBS, take leftmost bytes
529    let trim = prehash_bytes.len().saturating_sub(Scalar::<C>::NUM_LIMBS);
530    // from_be_bytes still works if len < Scalar::NUM_LIMBS
531    // we don't need to reduce because IntMod is up to modular equivalence
532    let z = Scalar::<C>::from_be_bytes_unchecked(&prehash_bytes[..prehash_bytes.len() - trim]);
533
534    let u1 = z.div_unsafe(&s);
535    let u2 = (&r).div_unsafe(&s);
536
537    let G = C::Point::GENERATOR;
538    // public key
539    let Q = pubkey;
540    let R = <C as IntrinsicCurve>::msm(&[u1, u2], &[G, Q]);
541    // For Coordinate<C>: IntMod, the internal implementation of is_identity will assert x, y
542    // coordinates of R are both reduced.
543    if R.is_identity() {
544        return Err(Error::new());
545    }
546    let (x_1, _) = R.into_coords();
547    // Scalar and Coordinate may be different byte lengths, so we use an inefficient reduction
548    let x_mod_n = Scalar::<C>::reduce_le_bytes(x_1.as_le_bytes());
549    if x_mod_n == r {
550        Ok(())
551    } else {
552        Err(Error::new())
553    }
554}
555
556impl<C: IntrinsicCurve> AsRef<AffinePoint<C>> for VerifyingKey<C> {
557    fn as_ref(&self) -> &AffinePoint<C> {
558        &self.inner.point
559    }
560}
561
562impl<C: IntrinsicCurve> AsRef<AffinePoint<C>> for PublicKey<C> {
563    fn as_ref(&self) -> &AffinePoint<C> {
564        &self.point
565    }
566}