Skip to main content

cryptography/public_key/
eddsa.rs

1//! Edwards-curve Digital Signature Algorithm style signatures.
2//!
3//! This module builds a Schnorr/EdDSA-style signature layer on top of the
4//! existing twisted Edwards arithmetic in [`ec_edwards`].  The signing
5//! equation follows the standard Edwards pattern:
6//!
7//! - choose a nonce scalar `k`
8//! - compute `R = k·G`
9//! - hash `R || A || M` to a scalar challenge `e`
10//! - return `S = k + e·d mod n`
11//!
12//! Verification checks `S·G = R + e·A`.
13//!
14//! This is intentionally *not* a byte-for-byte RFC 8032 Ed25519 clone: the
15//! key type stores the scalar directly instead of the RFC's hashed/clamped
16//! secret-key seed format.  The point arithmetic and point encoding still use
17//! the Edwards machinery in [`ec_edwards`], so the module is suitable for the
18//! crate's "pure Rust, explicit arithmetic" design.
19//!
20//! [`ec_edwards`]: crate::public_key::ec_edwards
21
22use core::fmt;
23
24use crate::hash::Digest;
25use crate::public_key::bigint::BigUint;
26use crate::public_key::ec_edwards::{EdwardsMulTable, EdwardsPoint, TwistedEdwardsCurve};
27use crate::public_key::io::{
28    decode_biguints, encode_biguints, pem_unwrap, pem_wrap, xml_unwrap, xml_wrap,
29};
30use crate::Csprng;
31
32const EDDSA_PUBLIC_LABEL: &str = "CRYPTOGRAPHY EDDSA PUBLIC KEY";
33const EDDSA_PRIVATE_LABEL: &str = "CRYPTOGRAPHY EDDSA PRIVATE KEY";
34
35/// Public key for the Edwards-curve signature layer.
36#[derive(Clone, Debug)]
37pub struct EdDsaPublicKey {
38    /// Edwards curve parameters.
39    curve: TwistedEdwardsCurve,
40    /// Public point `A = d·G`.
41    a_point: EdwardsPoint,
42    /// Cached precompute table for repeated `k·A` verification work.
43    a_table: EdwardsMulTable,
44}
45
46/// Private key for the Edwards-curve signature layer.
47#[derive(Clone)]
48pub struct EdDsaPrivateKey {
49    /// Edwards curve parameters.
50    curve: TwistedEdwardsCurve,
51    /// Secret scalar `d ∈ [1, n)`.
52    d: BigUint,
53    /// Cached public point `A = d·G`.
54    ///
55    /// Signing hashes `R || A || M`, so caching `A` avoids a full scalar
56    /// multiplication on every signature.
57    a_point: EdwardsPoint,
58}
59
60/// Signature pair `(R, S)`.
61#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct EdDsaSignature {
63    /// Nonce point `R = k·G`.
64    r_point: EdwardsPoint,
65    /// Response scalar `S = k + e·d mod n`.
66    s: BigUint,
67}
68
69/// Namespace wrapper for the Edwards-curve signature construction.
70pub struct EdDsa;
71
72impl PartialEq for EdDsaPublicKey {
73    fn eq(&self, other: &Self) -> bool {
74        self.curve.same_curve(&other.curve) && self.a_point == other.a_point
75    }
76}
77
78impl Eq for EdDsaPublicKey {}
79
80impl PartialEq for EdDsaPrivateKey {
81    fn eq(&self, other: &Self) -> bool {
82        self.curve.same_curve(&other.curve) && self.d == other.d
83    }
84}
85
86impl Eq for EdDsaPrivateKey {}
87
88impl EdDsaPublicKey {
89    /// Return the curve parameters.
90    #[must_use]
91    pub fn curve(&self) -> &TwistedEdwardsCurve {
92        &self.curve
93    }
94
95    /// Return the public point `A = d·G`.
96    #[must_use]
97    pub fn public_point(&self) -> &EdwardsPoint {
98        &self.a_point
99    }
100
101    /// Encode just the public point using the curve's compressed Edwards form.
102    #[must_use]
103    pub fn to_wire_bytes(&self) -> Vec<u8> {
104        self.curve.encode_point(&self.a_point)
105    }
106
107    /// Rebuild a public key from a compressed Edwards point plus explicit curve parameters.
108    #[must_use]
109    pub fn from_wire_bytes(curve: TwistedEdwardsCurve, bytes: &[u8]) -> Option<Self> {
110        let a_point = curve.decode_point(bytes)?;
111        if !validate_public_point(&curve, &a_point) {
112            return None;
113        }
114        let a_table = curve.precompute_mul_table(&a_point);
115        Some(Self {
116            curve,
117            a_point,
118            a_table,
119        })
120    }
121
122    /// Verify a signature over a raw message byte string.
123    #[must_use]
124    pub fn verify_message<H: Digest>(&self, message: &[u8], signature: &EdDsaSignature) -> bool {
125        if signature.s.is_zero() || signature.s >= self.curve.n {
126            return false;
127        }
128        if signature.r_point.is_neutral()
129            || !self.curve.is_on_curve(&signature.r_point)
130            || !point_in_prime_subgroup(&self.curve, &signature.r_point)
131        {
132            return false;
133        }
134
135        let challenge =
136            challenge_scalar::<H>(&self.curve, &signature.r_point, &self.a_point, message);
137
138        let lhs = self.curve.scalar_mul_base(&signature.s);
139        let rhs = self.curve.add(
140            &signature.r_point,
141            &self.curve.scalar_mul_cached(&self.a_table, &challenge),
142        );
143        lhs == rhs
144    }
145
146    /// Verify a byte-encoded signature produced by [`EdDsaPrivateKey::sign_message_bytes`].
147    #[must_use]
148    pub fn verify_message_bytes<H: Digest>(&self, message: &[u8], signature: &[u8]) -> bool {
149        let Some(signature) = EdDsaSignature::from_key_blob(signature, &self.curve) else {
150            return false;
151        };
152        self.verify_message::<H>(message, &signature)
153    }
154
155    /// Encode the public key in the crate-defined binary format.
156    ///
157    /// Field layout: `[p, a, d, n, Gx, Gy, Ax, Ay]`.
158    #[must_use]
159    pub fn to_key_blob(&self) -> Vec<u8> {
160        encode_biguints(&[
161            &self.curve.p,
162            &self.curve.a,
163            &self.curve.d,
164            &self.curve.n,
165            &self.curve.gx,
166            &self.curve.gy,
167            &self.a_point.x,
168            &self.a_point.y,
169        ])
170    }
171
172    /// Decode a public key from the crate-defined binary format.
173    #[must_use]
174    pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
175        let mut fields = decode_biguints(blob)?.into_iter();
176        let p = fields.next()?;
177        let a = fields.next()?;
178        let d = fields.next()?;
179        let n = fields.next()?;
180        let gx = fields.next()?;
181        let gy = fields.next()?;
182        let ax = fields.next()?;
183        let ay = fields.next()?;
184        if fields.next().is_some() {
185            return None;
186        }
187        let curve = TwistedEdwardsCurve::new(p, a, d, n, gx, gy)?;
188        // Reject non-canonical coordinates: is_on_curve reduces mod p, so a
189        // coordinate of p+k passes the curve equation.  Enforce ax, ay < p.
190        if ax >= curve.p || ay >= curve.p {
191            return None;
192        }
193        let a_point = EdwardsPoint::new(ax, ay);
194        if !validate_public_point(&curve, &a_point) {
195            return None;
196        }
197        let a_table = curve.precompute_mul_table(&a_point);
198        Some(Self {
199            curve,
200            a_point,
201            a_table,
202        })
203    }
204
205    #[must_use]
206    pub fn to_pem(&self) -> String {
207        pem_wrap(EDDSA_PUBLIC_LABEL, &self.to_key_blob())
208    }
209
210    #[must_use]
211    pub fn from_pem(pem: &str) -> Option<Self> {
212        let blob = pem_unwrap(EDDSA_PUBLIC_LABEL, pem)?;
213        Self::from_key_blob(&blob)
214    }
215
216    #[must_use]
217    pub fn to_xml(&self) -> String {
218        xml_wrap(
219            "EdDsaPublicKey",
220            &[
221                ("p", &self.curve.p),
222                ("a", &self.curve.a),
223                ("d", &self.curve.d),
224                ("n", &self.curve.n),
225                ("gx", &self.curve.gx),
226                ("gy", &self.curve.gy),
227                ("ax", &self.a_point.x),
228                ("ay", &self.a_point.y),
229            ],
230        )
231    }
232
233    #[must_use]
234    pub fn from_xml(xml: &str) -> Option<Self> {
235        let mut fields = xml_unwrap(
236            "EdDsaPublicKey",
237            &["p", "a", "d", "n", "gx", "gy", "ax", "ay"],
238            xml,
239        )?
240        .into_iter();
241        let p = fields.next()?;
242        let a = fields.next()?;
243        let d = fields.next()?;
244        let n = fields.next()?;
245        let gx = fields.next()?;
246        let gy = fields.next()?;
247        let ax = fields.next()?;
248        let ay = fields.next()?;
249        if fields.next().is_some() {
250            return None;
251        }
252        let curve = TwistedEdwardsCurve::new(p, a, d, n, gx, gy)?;
253        if ax >= curve.p || ay >= curve.p {
254            return None;
255        }
256        let a_point = EdwardsPoint::new(ax, ay);
257        if !validate_public_point(&curve, &a_point) {
258            return None;
259        }
260        let a_table = curve.precompute_mul_table(&a_point);
261        Some(Self {
262            curve,
263            a_point,
264            a_table,
265        })
266    }
267}
268
269impl EdDsaPrivateKey {
270    /// Return the curve parameters.
271    #[must_use]
272    pub fn curve(&self) -> &TwistedEdwardsCurve {
273        &self.curve
274    }
275
276    /// Return the private scalar `d ∈ [1, n)`.
277    #[must_use]
278    pub fn private_scalar(&self) -> &BigUint {
279        &self.d
280    }
281
282    /// Return the cached public point `A = d·G`.
283    #[must_use]
284    pub fn public_point(&self) -> &EdwardsPoint {
285        &self.a_point
286    }
287
288    /// Derive the matching public key `A = d·G`.
289    #[must_use]
290    pub fn to_public_key(&self) -> EdDsaPublicKey {
291        EdDsaPublicKey {
292            curve: self.curve.clone(),
293            a_point: self.a_point.clone(),
294            a_table: self.curve.precompute_mul_table(&self.a_point),
295        }
296    }
297
298    /// Sign with an explicit nonce scalar `k`.
299    ///
300    /// Reusing the same nonce with the same private key leaks the secret
301    /// scalar from two signatures, so this entry point is for deterministic
302    /// tests and fixed vectors only.
303    #[must_use]
304    pub fn sign_message_with_nonce<H: Digest>(
305        &self,
306        message: &[u8],
307        nonce: &BigUint,
308    ) -> Option<EdDsaSignature> {
309        if nonce.is_zero() || nonce >= &self.curve.n {
310            return None;
311        }
312
313        let r_point = self.curve.scalar_mul_base(nonce);
314        if r_point.is_neutral() {
315            return None;
316        }
317        let challenge = challenge_scalar::<H>(&self.curve, &r_point, &self.a_point, message);
318        let ed = BigUint::mod_mul(&challenge, &self.d, &self.curve.n);
319        let s = nonce.add_ref(&ed).modulo(&self.curve.n);
320        Some(EdDsaSignature { r_point, s })
321    }
322
323    /// Sign using a fresh random nonce.
324    #[must_use]
325    pub fn sign_message<H: Digest, R: Csprng>(
326        &self,
327        message: &[u8],
328        rng: &mut R,
329    ) -> Option<EdDsaSignature> {
330        loop {
331            let nonce = self.curve.random_scalar(rng);
332            if let Some(signature) = self.sign_message_with_nonce::<H>(message, &nonce) {
333                return Some(signature);
334            }
335        }
336    }
337
338    /// Sign and serialize in one step.
339    #[must_use]
340    pub fn sign_message_bytes<H: Digest, R: Csprng>(
341        &self,
342        message: &[u8],
343        rng: &mut R,
344    ) -> Option<Vec<u8>> {
345        let signature = self.sign_message::<H, R>(message, rng)?;
346        Some(signature.to_key_blob())
347    }
348
349    /// Encode the private key in the crate-defined binary format.
350    ///
351    /// Field layout: `[p, a, d, n, Gx, Gy, private]`.
352    #[must_use]
353    pub fn to_key_blob(&self) -> Vec<u8> {
354        encode_biguints(&[
355            &self.curve.p,
356            &self.curve.a,
357            &self.curve.d,
358            &self.curve.n,
359            &self.curve.gx,
360            &self.curve.gy,
361            &self.d,
362        ])
363    }
364
365    /// Decode a private key from the crate-defined binary format.
366    #[must_use]
367    pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
368        let mut fields = decode_biguints(blob)?.into_iter();
369        let p = fields.next()?;
370        let a = fields.next()?;
371        let d_curve = fields.next()?;
372        let n = fields.next()?;
373        let gx = fields.next()?;
374        let gy = fields.next()?;
375        let d = fields.next()?;
376        if fields.next().is_some() {
377            return None;
378        }
379        let curve = TwistedEdwardsCurve::new(p, a, d_curve, n, gx, gy)?;
380        if d.is_zero() || d >= curve.n {
381            return None;
382        }
383        let a_point = curve.scalar_mul_base(&d);
384        Some(Self { curve, d, a_point })
385    }
386
387    #[must_use]
388    pub fn to_pem(&self) -> String {
389        pem_wrap(EDDSA_PRIVATE_LABEL, &self.to_key_blob())
390    }
391
392    #[must_use]
393    pub fn from_pem(pem: &str) -> Option<Self> {
394        let blob = pem_unwrap(EDDSA_PRIVATE_LABEL, pem)?;
395        Self::from_key_blob(&blob)
396    }
397
398    #[must_use]
399    pub fn to_xml(&self) -> String {
400        xml_wrap(
401            "EdDsaPrivateKey",
402            &[
403                ("p", &self.curve.p),
404                ("a", &self.curve.a),
405                ("d", &self.curve.d),
406                ("n", &self.curve.n),
407                ("gx", &self.curve.gx),
408                ("gy", &self.curve.gy),
409                ("private", &self.d),
410            ],
411        )
412    }
413
414    #[must_use]
415    pub fn from_xml(xml: &str) -> Option<Self> {
416        let mut fields = xml_unwrap(
417            "EdDsaPrivateKey",
418            &["p", "a", "d", "n", "gx", "gy", "private"],
419            xml,
420        )?
421        .into_iter();
422        let p = fields.next()?;
423        let a = fields.next()?;
424        let d_curve = fields.next()?;
425        let n = fields.next()?;
426        let gx = fields.next()?;
427        let gy = fields.next()?;
428        let d = fields.next()?;
429        if fields.next().is_some() {
430            return None;
431        }
432        let curve = TwistedEdwardsCurve::new(p, a, d_curve, n, gx, gy)?;
433        if d.is_zero() || d >= curve.n {
434            return None;
435        }
436        let a_point = curve.scalar_mul_base(&d);
437        Some(Self { curve, d, a_point })
438    }
439}
440
441impl fmt::Debug for EdDsaPrivateKey {
442    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443        f.write_str("EdDsaPrivateKey(<redacted>)")
444    }
445}
446
447impl EdDsaSignature {
448    /// Return the nonce point `R`.
449    #[must_use]
450    pub fn nonce_point(&self) -> &EdwardsPoint {
451        &self.r_point
452    }
453
454    /// Return the response scalar `S`.
455    #[must_use]
456    pub fn response(&self) -> &BigUint {
457        &self.s
458    }
459
460    /// Encode the signature in the crate-defined binary format.
461    ///
462    /// Field layout: `[Rx, Ry, S]`.
463    #[must_use]
464    pub fn to_key_blob(&self) -> Vec<u8> {
465        encode_biguints(&[&self.r_point.x, &self.r_point.y, &self.s])
466    }
467
468    /// Decode a signature from the crate-defined binary format.
469    #[must_use]
470    pub fn from_key_blob(blob: &[u8], curve: &TwistedEdwardsCurve) -> Option<Self> {
471        let mut fields = decode_biguints(blob)?.into_iter();
472        let rx = fields.next()?;
473        let ry = fields.next()?;
474        let s = fields.next()?;
475        if fields.next().is_some() || s.is_zero() || s >= curve.n {
476            return None;
477        }
478        // Reject non-canonical coordinates: is_on_curve reduces mod p internally
479        // so a coordinate of p+k passes the curve equation, but the stored
480        // point would have non-canonical field representatives.  The wire-bytes
481        // path already enforces y < p; enforce the same here.
482        if rx >= curve.p || ry >= curve.p {
483            return None;
484        }
485        let r_point = EdwardsPoint::new(rx, ry);
486        if !curve.is_on_curve(&r_point) {
487            return None;
488        }
489        Some(Self { r_point, s })
490    }
491}
492
493impl EdDsa {
494    /// Generate a random key pair `(public, private)` for the chosen curve.
495    #[must_use]
496    pub fn generate<R: Csprng>(
497        curve: TwistedEdwardsCurve,
498        rng: &mut R,
499    ) -> (EdDsaPublicKey, EdDsaPrivateKey) {
500        let (d, a_point) = curve.generate_keypair(rng);
501        let public = EdDsaPublicKey {
502            curve: curve.clone(),
503            a_point: a_point.clone(),
504            a_table: curve.precompute_mul_table(&a_point),
505        };
506        let private = EdDsaPrivateKey { curve, d, a_point };
507        (public, private)
508    }
509
510    /// Derive a key pair from an explicit secret scalar.
511    #[must_use]
512    pub fn from_secret_scalar(
513        curve: TwistedEdwardsCurve,
514        secret: &BigUint,
515    ) -> Option<(EdDsaPublicKey, EdDsaPrivateKey)> {
516        if secret.is_zero() || secret >= &curve.n {
517            return None;
518        }
519        let a_point = curve.scalar_mul_base(secret);
520        if !validate_public_point(&curve, &a_point) {
521            return None;
522        }
523        Some((
524            EdDsaPublicKey {
525                curve: curve.clone(),
526                a_point: a_point.clone(),
527                a_table: curve.precompute_mul_table(&a_point),
528            },
529            EdDsaPrivateKey {
530                curve,
531                d: secret.clone(),
532                a_point,
533            },
534        ))
535    }
536}
537
538/// Verify that a public point is on-curve, in the prime-order subgroup, and non-neutral.
539fn validate_public_point(curve: &TwistedEdwardsCurve, point: &EdwardsPoint) -> bool {
540    !point.is_neutral() && curve.is_on_curve(point) && point_in_prime_subgroup(curve, point)
541}
542
543/// Check subgroup membership by verifying `n·P = 0`.
544fn point_in_prime_subgroup(curve: &TwistedEdwardsCurve, point: &EdwardsPoint) -> bool {
545    curve.scalar_mul(point, &curve.n).is_neutral()
546}
547
548/// Compute the EdDSA-style challenge scalar `e = H(encode(R) || encode(A) || M) mod n`.
549fn challenge_scalar<H: Digest>(
550    curve: &TwistedEdwardsCurve,
551    r_point: &EdwardsPoint,
552    a_point: &EdwardsPoint,
553    message: &[u8],
554) -> BigUint {
555    let mut transcript = curve.encode_point(r_point);
556    transcript.extend_from_slice(&curve.encode_point(a_point));
557    transcript.extend_from_slice(message);
558    BigUint::from_be_bytes(&H::digest(&transcript)).modulo(&curve.n)
559}
560
561#[cfg(test)]
562mod tests {
563    use super::{EdDsa, EdDsaPrivateKey, EdDsaPublicKey, EdDsaSignature};
564    use crate::public_key::bigint::BigUint;
565    use crate::public_key::ec_edwards::ed25519;
566    use crate::public_key::io::encode_biguints;
567    use crate::{CtrDrbgAes256, Sha512};
568
569    fn rng() -> CtrDrbgAes256 {
570        CtrDrbgAes256::new(&[0x5a; 48])
571    }
572
573    #[test]
574    fn roundtrip_sign_verify_ed25519() {
575        let curve = ed25519();
576        let (public, private) = EdDsa::generate(curve, &mut rng());
577        let sig = private
578            .sign_message::<Sha512, _>(b"edwards signature", &mut rng())
579            .expect("sign");
580        assert!(public.verify_message::<Sha512>(b"edwards signature", &sig));
581        assert!(!public.verify_message::<Sha512>(b"wrong", &sig));
582    }
583
584    #[test]
585    fn sign_with_explicit_nonce_roundtrip() {
586        let curve = ed25519();
587        let secret = BigUint::from_u64(7);
588        let nonce = BigUint::from_u64(11);
589        let (public, private) = EdDsa::from_secret_scalar(curve, &secret).expect("explicit secret");
590        let sig = private
591            .sign_message_with_nonce::<Sha512>(b"abc", &nonce)
592            .expect("explicit nonce");
593        assert!(public.verify_message::<Sha512>(b"abc", &sig));
594    }
595
596    #[test]
597    fn sign_message_with_nonce_is_repeatable() {
598        let curve = ed25519();
599        let secret = BigUint::from_u64(7);
600        let nonce = BigUint::from_u64(11);
601        let (_public, private) =
602            EdDsa::from_secret_scalar(curve, &secret).expect("explicit secret");
603        let lhs = private
604            .sign_message_with_nonce::<Sha512>(b"abc", &nonce)
605            .expect("first signature");
606        let rhs = private
607            .sign_message_with_nonce::<Sha512>(b"abc", &nonce)
608            .expect("second signature");
609        assert_eq!(lhs, rhs);
610    }
611
612    #[test]
613    fn tampered_signature_is_rejected() {
614        let curve = ed25519();
615        let (public, private) = EdDsa::generate(curve, &mut rng());
616        let mut sig = private
617            .sign_message::<Sha512, _>(b"tamper", &mut rng())
618            .expect("sign");
619        sig.s = sig.s.add_ref(&BigUint::one()).modulo(&public.curve().n);
620        if sig.s.is_zero() {
621            sig.s = BigUint::one();
622        }
623        assert!(!public.verify_message::<Sha512>(b"tamper", &sig));
624    }
625
626    #[test]
627    fn key_serialization_roundtrip() {
628        let curve = ed25519();
629        let (public, private) = EdDsa::generate(curve, &mut rng());
630
631        let public_bin = public.to_key_blob();
632        let public_pem = public.to_pem();
633        let public_xml = public.to_xml();
634        assert_eq!(
635            EdDsaPublicKey::from_key_blob(&public_bin).expect("public binary"),
636            public
637        );
638        assert_eq!(
639            EdDsaPublicKey::from_pem(&public_pem).expect("public pem"),
640            public
641        );
642        assert_eq!(
643            EdDsaPublicKey::from_xml(&public_xml).expect("public xml"),
644            public
645        );
646
647        let private_bin = private.to_key_blob();
648        let private_pem = private.to_pem();
649        let private_xml = private.to_xml();
650        let private_round = EdDsaPrivateKey::from_key_blob(&private_bin).expect("private binary");
651        assert_eq!(private_round, private);
652        assert_eq!(private_round.to_public_key(), public);
653        assert_eq!(
654            EdDsaPrivateKey::from_pem(&private_pem).expect("private pem"),
655            private
656        );
657        assert_eq!(
658            EdDsaPrivateKey::from_xml(&private_xml).expect("private xml"),
659            private
660        );
661    }
662
663    #[test]
664    fn public_bytes_roundtrip() {
665        let curve = ed25519();
666        let (public, _) = EdDsa::generate(curve.clone(), &mut rng());
667        let bytes = public.to_wire_bytes();
668        let round = EdDsaPublicKey::from_wire_bytes(curve, &bytes).expect("public bytes");
669        assert_eq!(round, public);
670    }
671
672    #[test]
673    fn signature_binary_roundtrip() {
674        let curve = ed25519();
675        let (public, private) = EdDsa::generate(curve, &mut rng());
676        let sig = private
677            .sign_message::<Sha512, _>(b"serialize", &mut rng())
678            .expect("sign");
679        let blob = sig.to_key_blob();
680        let decoded = EdDsaSignature::from_key_blob(&blob, public.curve()).expect("decode sig");
681        assert_eq!(decoded, sig);
682        assert!(public.verify_message::<Sha512>(b"serialize", &decoded));
683    }
684
685    #[test]
686    fn signature_binary_rejects_out_of_range_s() {
687        let curve = ed25519();
688        let base = curve.base_point();
689        let blob = encode_biguints(&[&base.x, &base.y, &curve.n]);
690        assert!(EdDsaSignature::from_key_blob(&blob, &curve).is_none());
691    }
692}