Skip to main content

sqisign_verify/
types.rs

1//!
2//! Wire formats exactly match the v2.0 specification.
3
4use crate::ec::basis::ec_curve_to_basis_2f_to_hint;
5use crate::ec::{EcCurve, EcPoint};
6use crate::fp::{Fp2, FpBackend};
7use crate::params::{Level1, SecurityLevel};
8use crate::precomp::LevelPrecomp;
9use hybrid_array::typenum::Unsigned;
10use hybrid_array::Array;
11
12/// Write a byte slice as lowercase hex into a formatter.
13pub(crate) fn fmt_hex(f: &mut core::fmt::Formatter<'_>, bytes: &[u8]) -> core::fmt::Result {
14    for &b in bytes {
15        write!(f, "{b:02x}")?;
16    }
17    Ok(())
18}
19
20/// Write the little-endian u64 limbs of a [`Scalar`] as a hex byte string.
21pub(crate) fn fmt_scalar<L>(f: &mut core::fmt::Formatter<'_>, s: &Scalar<L>) -> core::fmt::Result
22where
23    L: FpBackend,
24{
25    for &limb in s.digits.as_slice() {
26        for &b in &limb.to_le_bytes() {
27            write!(f, "{b:02x}")?;
28        }
29    }
30    Ok(())
31}
32
33/// Write an `Fp2` element as a hex byte string of its canonical encoding.
34pub(crate) fn fmt_fp2<L: FpBackend>(
35    f: &mut core::fmt::Formatter<'_>,
36    v: &Fp2<L>,
37) -> core::fmt::Result {
38    fmt_hex(f, &v.encode())
39}
40
41/// A fixed-width multi-precision integer for scalars, matrix entries,
42/// and challenge coefficients.
43///
44/// Represented as `NWORDS_ORDER` little-endian 64-bit limbs.
45#[derive(Clone)]
46pub struct Scalar<L: SecurityLevel = Level1> {
47    pub(crate) digits: Array<u64, L::MpLimbs>,
48}
49
50impl<L: FpBackend> core::fmt::Debug for Scalar<L> {
51    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52        f.write_str("Scalar(")?;
53        fmt_scalar(f, self)?;
54        f.write_str(")")
55    }
56}
57
58impl<L: FpBackend> Scalar<L> {
59    #[inline]
60    pub fn digits(&self) -> &[u64] {
61        self.digits.as_slice()
62    }
63}
64
65impl<L: FpBackend> Default for Scalar<L> {
66    fn default() -> Self {
67        Self {
68            digits: Array::default(),
69        }
70    }
71}
72
73/// SQIsign public key: a Montgomery curve coefficient plus a torsion hint byte.
74///
75/// Wire size: 65 bytes (Level 1), 97 bytes (Level 3), 129 bytes (Level 5).
76///
77/// # Verify a signature
78///
79/// Use [`pk.verify(msg, &sig)`](signature::Verifier::verify) via the
80/// [`Verifier`](signature::Verifier) trait. It accepts any signature type:
81/// [`Signature`], [`ExpandedSignature`](crate::ExpandedSignature),
82/// [`CompressedSignature`](crate::CompressedSignature), or
83/// [`AnySignature`](crate::formats::AnySignature).
84///
85/// ```
86/// use hex_literal::hex;
87/// use sqisign_verify::{PublicKey, Signature, Verifier};
88///
89/// # fn main() -> Result<(), sqisign_verify::Error> {
90/// let pk_bytes = hex!(
91///     "07CCD21425136F6E865E497D2D4D208F0054AD81372066E817480787AAF7B202"
92///     "9550C89E892D618CE3230F23510BFBE68FCCDDAEA51DB1436B462ADFAF008A01"
93///     "0B"
94/// );
95/// let sig_bytes = hex!(
96///     "84228651F271B0F39F2F19F2E8718F31ED3365AC9E5CB303AFE663D0CFC11F04"
97///     "55D891B0CA6C7E653F9BA2667730BB77BEFE1B1A31828404284AF8FD7BAACC01"
98///     "0001D974B5CA671FF65708D8B462A5A84A1443EE9B5FED7218767C9D85CEED04"
99///     "DB0A69A2F6EC3BE835B3B2624B9A0DF68837AD00BCACC27D1EC806A448402674"
100///     "71D86EFF3447018ADB0A6551EE8322AB30010202"
101/// );
102/// let msg = hex!(
103///     "D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556A"
104///     "C8"
105/// );
106///
107/// let pk: PublicKey = PublicKey::from_bytes(&pk_bytes)?;
108/// let sig: Signature = Signature::from_bytes(&sig_bytes)?;
109/// pk.verify(&msg, &sig)?;
110/// # Ok(())
111/// # }
112/// ```
113///
114/// # Decode and re-encode
115///
116/// ```
117/// use hex_literal::hex;
118/// use sqisign_verify::PublicKey;
119///
120/// # fn main() -> Result<(), sqisign_verify::Error> {
121/// let pk_bytes = hex!(
122///     "07CCD21425136F6E865E497D2D4D208F0054AD81372066E817480787AAF7B202"
123///     "9550C89E892D618CE3230F23510BFBE68FCCDDAEA51DB1436B462ADFAF008A01"
124///     "0B"
125/// );
126/// let pk: PublicKey = PublicKey::from_bytes(&pk_bytes)?;
127/// assert_eq!(pk_bytes, pk.to_bytes().as_slice());
128/// # Ok(())
129/// # }
130/// ```
131///
132/// # Display
133///
134/// `Display` prints the wire-format bytes as lowercase hex:
135///
136/// ```
137/// # use hex_literal::hex;
138/// # use sqisign_verify::PublicKey;
139/// # fn main() -> Result<(), sqisign_verify::Error> {
140/// # let pk_bytes = hex!(
141/// #     "07CCD21425136F6E865E497D2D4D208F0054AD81372066E817480787AAF7B202"
142/// #     "9550C89E892D618CE3230F23510BFBE68FCCDDAEA51DB1436B462ADFAF008A01"
143/// #     "0B"
144/// # );
145/// # let pk: PublicKey = PublicKey::from_bytes(&pk_bytes)?;
146/// let hex_str = format!("{pk}");
147/// assert!(hex_str.starts_with("07ccd214"));
148/// # Ok(())
149/// # }
150/// ```
151#[derive(Clone)]
152pub struct PublicKey<L: SecurityLevel = Level1> {
153    pub(crate) curve: EcCurve<L>,
154    pub(crate) hint_pk: u8,
155}
156
157impl<L: FpBackend> core::fmt::Debug for PublicKey<L> {
158    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
159        f.write_str("PublicKey { curve_a: ")?;
160        fmt_fp2(f, &self.curve.a)?;
161        write!(f, ", hint: 0x{:02x}", self.hint_pk)?;
162        f.write_str(" }")
163    }
164}
165
166impl<L: FpBackend> core::fmt::Display for PublicKey<L> {
167    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
168        fmt_hex(f, &self.to_bytes())
169    }
170}
171
172impl<L: FpBackend> PublicKey<L> {
173    #[doc(hidden)]
174    #[inline]
175    pub fn new(curve: EcCurve<L>, hint_pk: u8) -> Self {
176        Self { curve, hint_pk }
177    }
178
179    #[inline]
180    pub fn curve(&self) -> &EcCurve<L> {
181        &self.curve
182    }
183
184    #[inline]
185    pub fn hint_pk(&self) -> u8 {
186        self.hint_pk
187    }
188}
189
190impl<L: FpBackend> Default for PublicKey<L> {
191    fn default() -> Self {
192        Self {
193            curve: EcCurve::default(),
194            hint_pk: 0,
195        }
196    }
197}
198
199/// SQIsign signature (standard wire format).
200///
201/// Wire size: 148 bytes (Level 1), 224 bytes (Level 3), 292 bytes (Level 5).
202///
203/// # Verify
204///
205/// Use [`pk.verify(msg, &sig)`](signature::Verifier::verify) via the
206/// [`Verifier`](signature::Verifier) trait:
207///
208/// ```
209/// use hex_literal::hex;
210/// use sqisign_verify::{PublicKey, Signature, Verifier};
211///
212/// # fn main() -> Result<(), sqisign_verify::Error> {
213/// let pk_bytes = hex!(
214///     "07CCD21425136F6E865E497D2D4D208F0054AD81372066E817480787AAF7B202"
215///     "9550C89E892D618CE3230F23510BFBE68FCCDDAEA51DB1436B462ADFAF008A01"
216///     "0B"
217/// );
218/// let sig_bytes = hex!(
219///     "84228651F271B0F39F2F19F2E8718F31ED3365AC9E5CB303AFE663D0CFC11F04"
220///     "55D891B0CA6C7E653F9BA2667730BB77BEFE1B1A31828404284AF8FD7BAACC01"
221///     "0001D974B5CA671FF65708D8B462A5A84A1443EE9B5FED7218767C9D85CEED04"
222///     "DB0A69A2F6EC3BE835B3B2624B9A0DF68837AD00BCACC27D1EC806A448402674"
223///     "71D86EFF3447018ADB0A6551EE8322AB30010202"
224/// );
225/// let msg = hex!(
226///     "D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556A"
227///     "C8"
228/// );
229///
230/// let pk: PublicKey = PublicKey::from_bytes(&pk_bytes)?;
231/// let sig: Signature = Signature::from_bytes(&sig_bytes)?;
232/// pk.verify(&msg, &sig)?;
233/// # Ok(())
234/// # }
235/// ```
236///
237/// # Serialize
238///
239/// ```
240/// # use hex_literal::hex;
241/// # use sqisign_verify::{PublicKey, Signature};
242/// # fn main() -> Result<(), sqisign_verify::Error> {
243/// # let sig_bytes = hex!(
244/// #     "84228651F271B0F39F2F19F2E8718F31ED3365AC9E5CB303AFE663D0CFC11F04"
245/// #     "55D891B0CA6C7E653F9BA2667730BB77BEFE1B1A31828404284AF8FD7BAACC01"
246/// #     "0001D974B5CA671FF65708D8B462A5A84A1443EE9B5FED7218767C9D85CEED04"
247/// #     "DB0A69A2F6EC3BE835B3B2624B9A0DF68837AD00BCACC27D1EC806A448402674"
248/// #     "71D86EFF3447018ADB0A6551EE8322AB30010202"
249/// # );
250/// # let sig: Signature = Signature::from_bytes(&sig_bytes)?;
251/// let wire = sig.to_bytes();
252/// assert_eq!(wire.len(), 148); // Level 1 standard signature
253/// assert_eq!(wire.as_slice(), &sig_bytes);
254/// # Ok(())
255/// # }
256/// ```
257#[derive(Clone)]
258pub struct Signature<L: SecurityLevel = Level1> {
259    pub(crate) e_aux_a: Fp2<L>,
260    pub(crate) backtracking: u8,
261    pub(crate) two_resp_length: u8,
262    pub(crate) mat: [[Scalar<L>; 2]; 2],
263    pub(crate) chall_coeff: Scalar<L>,
264    pub(crate) hint_aux: u8,
265    pub(crate) hint_chall: u8,
266}
267
268impl<L: FpBackend> Signature<L> {
269    #[inline]
270    pub fn e_aux_a(&self) -> &Fp2<L> {
271        &self.e_aux_a
272    }
273
274    #[inline]
275    pub fn backtracking(&self) -> u8 {
276        self.backtracking
277    }
278
279    #[inline]
280    pub fn two_resp_length(&self) -> u8 {
281        self.two_resp_length
282    }
283
284    #[inline]
285    pub fn mat(&self) -> &[[Scalar<L>; 2]; 2] {
286        &self.mat
287    }
288
289    #[inline]
290    pub fn chall_coeff(&self) -> &Scalar<L> {
291        &self.chall_coeff
292    }
293
294    #[inline]
295    pub fn hint_aux(&self) -> u8 {
296        self.hint_aux
297    }
298
299    #[inline]
300    pub fn hint_chall(&self) -> u8 {
301        self.hint_chall
302    }
303}
304
305#[doc(hidden)]
306impl<L: FpBackend> Signature<L> {
307    #[inline]
308    pub fn set_e_aux_a(&mut self, v: Fp2<L>) {
309        self.e_aux_a = v;
310    }
311
312    #[inline]
313    pub fn set_backtracking(&mut self, v: u8) {
314        self.backtracking = v;
315    }
316
317    #[inline]
318    pub fn set_two_resp_length(&mut self, v: u8) {
319        self.two_resp_length = v;
320    }
321
322    #[inline]
323    pub fn set_hint_aux(&mut self, v: u8) {
324        self.hint_aux = v;
325    }
326
327    #[inline]
328    pub fn set_hint_chall(&mut self, v: u8) {
329        self.hint_chall = v;
330    }
331
332    #[inline]
333    pub fn mat_mut(&mut self) -> &mut [[Scalar<L>; 2]; 2] {
334        &mut self.mat
335    }
336
337    #[inline]
338    pub fn chall_coeff_mut(&mut self) -> &mut Scalar<L> {
339        &mut self.chall_coeff
340    }
341
342    #[inline]
343    pub fn scalar_digits_mut(s: &mut Scalar<L>) -> &mut [u64] {
344        s.digits.as_mut_slice()
345    }
346}
347
348impl<L: FpBackend> Default for Signature<L> {
349    fn default() -> Self {
350        Self {
351            e_aux_a: Fp2::zero(),
352            backtracking: 0,
353            two_resp_length: 0,
354            mat: [
355                [Scalar::default(), Scalar::default()],
356                [Scalar::default(), Scalar::default()],
357            ],
358            chall_coeff: Scalar::default(),
359            hint_aux: 0,
360            hint_chall: 0,
361        }
362    }
363}
364
365impl<L: FpBackend> core::fmt::Debug for Signature<L> {
366    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
367        f.write_str("Signature { e_aux_a: ")?;
368        fmt_fp2(f, &self.e_aux_a)?;
369        write!(
370            f,
371            ", bt: {}, trl: {}, mat: [[",
372            self.backtracking, self.two_resp_length
373        )?;
374        fmt_scalar(f, &self.mat[0][0])?;
375        f.write_str(", ")?;
376        fmt_scalar(f, &self.mat[0][1])?;
377        f.write_str("], [")?;
378        fmt_scalar(f, &self.mat[1][0])?;
379        f.write_str(", ")?;
380        fmt_scalar(f, &self.mat[1][1])?;
381        f.write_str("]], chall: ")?;
382        fmt_scalar(f, &self.chall_coeff)?;
383        write!(
384            f,
385            ", hint_aux: 0x{:02x}, hint_chall: 0x{:02x} }}",
386            self.hint_aux, self.hint_chall
387        )
388    }
389}
390
391impl<L: FpBackend> core::fmt::Display for Signature<L> {
392    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
393        fmt_hex(f, &self.to_bytes())
394    }
395}
396
397pub(crate) fn encode_digits(dst: &mut [u8], src: &[u64], nbytes: usize) {
398    let mut pos = 0;
399    for &d in src {
400        if pos >= nbytes {
401            break;
402        }
403        let bytes = d.to_le_bytes();
404        let take = core::cmp::min(8, nbytes - pos);
405        dst[pos..pos + take].copy_from_slice(&bytes[..take]);
406        pos += take;
407    }
408}
409
410pub(crate) fn decode_digits(dst: &mut [u64], src: &[u8], nbytes: usize) {
411    dst.fill(0);
412    for (i, &byte) in src.iter().enumerate().take(nbytes) {
413        let digit_idx = i / 8;
414        let byte_idx = i % 8;
415        if digit_idx < dst.len() {
416            dst[digit_idx] |= (byte as u64) << (byte_idx * 8);
417        }
418    }
419}
420
421pub(crate) fn proj_to_bytes<L: FpBackend>(dst: &mut [u8], x: &Fp2<L>, z: &Fp2<L>) -> usize {
422    let z_inv = z.inv();
423    let affine = x.mul(&z_inv);
424    let enc = affine.encode();
425    let len = enc.len();
426    dst[..len].copy_from_slice(&enc);
427    len
428}
429
430pub(crate) fn proj_from_bytes<L: FpBackend>(
431    x: &mut Fp2<L>,
432    z: &mut Fp2<L>,
433    src: &[u8],
434) -> Result<usize, crate::Error> {
435    let fp2_len = <L as SecurityLevel>::Fp2EncodedBytes::USIZE;
436    *x = Fp2::<L>::decode(&src[..fp2_len]).ok_or(crate::Error::MalformedInput)?;
437    *z = Fp2::one();
438    Ok(fp2_len)
439}
440
441impl<L: FpBackend> PublicKey<L> {
442    /// Encode a public key to bytes (wire format).
443    pub fn to_bytes(&self) -> Array<u8, L::PkLen> {
444        let mut enc = Array::<u8, L::PkLen>::default();
445        let mut pos = proj_to_bytes::<L>(&mut enc, &self.curve.a, &self.curve.c);
446        enc[pos] = self.hint_pk;
447        pos += 1;
448        debug_assert_eq!(pos, L::PkLen::USIZE);
449        enc
450    }
451
452    /// Decode a public key from bytes (wire format).
453    ///
454    /// Rejects non-canonical `hint_pk` values to ensure each logical
455    /// public key has exactly one valid wire encoding.
456    pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::Error>
457    where
458        L: LevelPrecomp,
459    {
460        if bytes.len() != L::PkLen::USIZE {
461            return Err(crate::Error::InvalidLength);
462        }
463        let mut pk = PublicKey::<L>::default();
464        let mut pos = proj_from_bytes::<L>(&mut pk.curve.a, &mut pk.curve.c, bytes)?;
465        pk.hint_pk = bytes[pos];
466        pos += 1;
467        debug_assert_eq!(pos, L::PkLen::USIZE);
468
469        let mut check_curve = pk.curve.clone();
470        let (_, canonical_hint) = ec_curve_to_basis_2f_to_hint::<L>(
471            &mut check_curve,
472            L::F_CHR,
473            L::basis_e0_px_bytes(),
474            L::basis_e0_qx_bytes(),
475            L::p_cofactor_for_2f(),
476            L::p_cofactor_for_2f_bitlength() as usize,
477            L::torsion_even_power(),
478        )
479        .map_err(|()| crate::Error::MalformedInput)?;
480        if pk.hint_pk != canonical_hint {
481            return Err(crate::Error::MalformedInput);
482        }
483
484        Ok(pk)
485    }
486}
487
488impl<L: FpBackend> Signature<L> {
489    /// Number of bytes per matrix entry in the wire format.
490    fn matrix_entry_bytes() -> usize {
491        (L::E_RSP as usize + 9) / 8
492    }
493
494    /// Number of bytes for the challenge coefficient.
495    fn chall_coeff_bytes() -> usize {
496        L::LAMBDA as usize / 8
497    }
498
499    /// Encode a signature to bytes (wire format).
500    pub fn to_bytes(&self) -> Array<u8, L::SigLen> {
501        let mut enc = Array::<u8, L::SigLen>::default();
502        let mut pos = 0;
503
504        // E_aux_A (Fp2 element)
505        let fp2_enc = self.e_aux_a.encode();
506        enc[pos..pos + fp2_enc.len()].copy_from_slice(&fp2_enc);
507        pos += fp2_enc.len();
508
509        // Metadata bytes
510        enc[pos] = self.backtracking;
511        pos += 1;
512        enc[pos] = self.two_resp_length;
513        pos += 1;
514
515        // 2x2 scalar matrix
516        let mat_bytes = Self::matrix_entry_bytes();
517        for row in &self.mat {
518            for entry in row {
519                encode_digits(&mut enc[pos..], entry.digits.as_slice(), mat_bytes);
520                pos += mat_bytes;
521            }
522        }
523
524        // Challenge coefficient
525        let chall_bytes = Self::chall_coeff_bytes();
526        encode_digits(
527            &mut enc[pos..],
528            self.chall_coeff.digits.as_slice(),
529            chall_bytes,
530        );
531        pos += chall_bytes;
532
533        // Hint bytes
534        enc[pos] = self.hint_aux;
535        pos += 1;
536        enc[pos] = self.hint_chall;
537        pos += 1;
538
539        debug_assert_eq!(pos, L::SigLen::USIZE);
540        enc
541    }
542
543    /// Decode a signature from bytes (wire format).
544    pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::Error> {
545        if bytes.len() != L::SigLen::USIZE {
546            return Err(crate::Error::InvalidLength);
547        }
548
549        let mut sig = Signature::<L>::default();
550        let mut pos = 0;
551
552        // E_aux_A
553        let fp2_len = <L as SecurityLevel>::Fp2EncodedBytes::USIZE;
554        sig.e_aux_a =
555            Fp2::decode(&bytes[pos..pos + fp2_len]).ok_or(crate::Error::MalformedInput)?;
556        pos += fp2_len;
557
558        // Metadata
559        sig.backtracking = bytes[pos];
560        pos += 1;
561        sig.two_resp_length = bytes[pos];
562        pos += 1;
563
564        // 2x2 scalar matrix
565        let mat_bytes = Self::matrix_entry_bytes();
566        for row in sig.mat.iter_mut() {
567            for entry in row.iter_mut() {
568                decode_digits(entry.digits.as_mut_slice(), &bytes[pos..], mat_bytes);
569                pos += mat_bytes;
570            }
571        }
572
573        // Challenge coefficient
574        let chall_bytes = Self::chall_coeff_bytes();
575        decode_digits(
576            sig.chall_coeff.digits.as_mut_slice(),
577            &bytes[pos..],
578            chall_bytes,
579        );
580        pos += chall_bytes;
581
582        // Hints
583        sig.hint_aux = bytes[pos];
584        pos += 1;
585        sig.hint_chall = bytes[pos];
586        pos += 1;
587
588        debug_assert_eq!(pos, L::SigLen::USIZE);
589        Ok(sig)
590    }
591}
592
593impl<L: FpBackend + LevelPrecomp> TryFrom<&[u8]> for PublicKey<L> {
594    type Error = crate::Error;
595
596    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
597        Self::from_bytes(bytes)
598    }
599}
600
601impl<L: FpBackend> TryFrom<&[u8]> for Signature<L> {
602    type Error = crate::Error;
603
604    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
605        Self::from_bytes(bytes)
606    }
607}
608
609impl<L: FpBackend> From<Signature<L>> for Array<u8, L::SigLen> {
610    fn from(sig: Signature<L>) -> Self {
611        sig.to_bytes()
612    }
613}
614
615impl<L: FpBackend> signature::SignatureEncoding for Signature<L>
616where
617    Array<u8, L::SigLen>: Send + Sync,
618{
619    type Repr = Array<u8, L::SigLen>;
620}
621
622impl<L: FpBackend + crate::precomp::LevelPrecomp> signature::Verifier<Signature<L>>
623    for PublicKey<L>
624{
625    fn verify(&self, msg: &[u8], sig: &Signature<L>) -> Result<(), signature::Error> {
626        crate::verify::protocols_verify(self, msg, sig).map_err(|_| signature::Error::new())
627    }
628}
629
630/// Encode an elliptic curve `(A:C)` to bytes (affine A-coefficient).
631pub fn ec_curve_to_bytes<L: FpBackend>(dst: &mut [u8], curve: &EcCurve<L>) -> usize {
632    proj_to_bytes::<L>(dst, &curve.a, &curve.c)
633}
634
635/// Decode an elliptic curve from bytes (affine A-coefficient, C=1).
636pub fn ec_curve_from_bytes<L: FpBackend>(
637    curve: &mut EcCurve<L>,
638    src: &[u8],
639) -> Result<usize, crate::Error> {
640    *curve = EcCurve::default();
641    proj_from_bytes::<L>(&mut curve.a, &mut curve.c, src)
642}
643
644/// Encode a projective point `(X:Z)` to bytes (affine X-coordinate).
645pub fn ec_point_to_bytes<L: FpBackend>(dst: &mut [u8], point: &EcPoint<L>) -> usize {
646    proj_to_bytes::<L>(dst, &point.x, &point.z)
647}
648
649/// Decode a projective point from bytes (affine X-coordinate, Z=1).
650pub fn ec_point_from_bytes<L: FpBackend>(
651    point: &mut EcPoint<L>,
652    src: &[u8],
653) -> Result<usize, crate::Error> {
654    proj_from_bytes::<L>(&mut point.x, &mut point.z, src)
655}