Skip to main content

_synta/
crypto_keys.rs

1//! Python bindings for generic public and private key operations.
2//!
3//! Exposes [`PyPublicKey`] and [`PyPrivateKey`] as pyo3 classes supporting
4//! RSA, EC, EdDSA, and DSA keys via the `synta-certificate` backend traits.
5//! No direct `openssl::*` imports are used here.
6
7use pyo3::exceptions::PyValueError;
8use pyo3::prelude::*;
9use pyo3::types::PyBytes;
10use synta_certificate::{BackendPrivateKey, BackendPublicKey, KeyDecryptor, KeyEncryptor};
11
12// ── PublicKey ─────────────────────────────────────────────────────────────────
13
14/// An asymmetric public key.
15///
16/// Supports RSA, EC (P-256, P-384, P-521), Ed25519, Ed448, and DSA keys.
17/// Load from PEM or SubjectPublicKeyInfo DER; serialize back to PEM or DER.
18/// RSA keys can encrypt data with OAEP or PKCS\#1 v1.5 padding.
19///
20/// ```python,ignore
21/// import synta
22///
23/// # Load an RSA public key from a PEM file:
24/// with open("rsa_pub.pem", "rb") as f:
25///     pub = synta.PublicKey.from_pem(f.read())
26/// print(pub.key_type)   # "rsa"
27/// print(pub.key_size)   # e.g. 2048
28///
29/// # Encrypt with OAEP (SHA-256):
30/// ct = pub.rsa_oaep_encrypt(b"secret", "sha256")
31///
32/// # Load an EC public key from SPKI DER:
33/// ec_pub = synta.PublicKey.from_der(spki_der)
34/// print(ec_pub.curve_name)  # "P-256"
35/// ```
36#[pyclass(frozen, name = "PublicKey")]
37pub struct PyPublicKey {
38    pub(crate) inner: BackendPublicKey,
39}
40
41#[pymethods]
42impl PyPublicKey {
43    /// Load a public key from PEM-encoded SubjectPublicKeyInfo data.
44    ///
45    /// Supports RSA, EC (P-256, P-384, P-521), Ed25519, Ed448, and DSA keys.
46    ///
47    /// ```python,ignore
48    /// with open("pubkey.pem", "rb") as f:
49    ///     pub = synta.PublicKey.from_pem(f.read())
50    /// ```
51    #[staticmethod]
52    fn from_pem(data: &[u8]) -> PyResult<Self> {
53        let inner =
54            BackendPublicKey::from_pem(data).map_err(|e| PyValueError::new_err(format!("{e}")))?;
55        Ok(Self { inner })
56    }
57
58    /// Construct an RSA public key from raw big-endian modulus *n* and public-exponent *e* bytes.
59    ///
60    /// This is the inverse of the :attr:`modulus` and :attr:`public_exponent` getters.
61    /// Raises :exc:`ValueError` if the inputs do not encode a valid RSA key.
62    ///
63    /// ```python,ignore
64    /// # n and e are big-endian bytes (e.g. extracted from a PKCS#11 token)
65    /// pub = synta.PublicKey.from_rsa_components(n, e)
66    /// assert pub.key_type == "rsa"
67    /// ```
68    #[staticmethod]
69    fn from_rsa_components(n: &[u8], e: &[u8]) -> PyResult<Self> {
70        let inner = BackendPublicKey::from_rsa_components(n, e)
71            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
72        Ok(Self { inner })
73    }
74
75    /// Construct an EC public key from affine coordinates *x* and *y* (big-endian
76    /// bytes) and a NIST curve name (``"P-256"``, ``"P-384"``, or ``"P-521"``).
77    ///
78    /// This is the inverse of the :attr:`x`, :attr:`y`, and :attr:`curve_name` getters.
79    /// Raises :exc:`ValueError` for unknown curve names or invalid coordinates.
80    ///
81    /// ```python,ignore
82    /// pub = synta.PublicKey.from_ec_components(x_bytes, y_bytes, "P-256")
83    /// assert pub.key_type == "ec"
84    /// ```
85    #[staticmethod]
86    fn from_ec_components(x: &[u8], y: &[u8], curve: &str) -> PyResult<Self> {
87        let inner = BackendPublicKey::from_ec_components(x, y, curve)
88            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
89        Ok(Self { inner })
90    }
91
92    /// Load a public key from a DER-encoded SubjectPublicKeyInfo structure.
93    ///
94    /// ```python,ignore
95    /// with open("pubkey.der", "rb") as f:
96    ///     pub = synta.PublicKey.from_der(f.read())
97    /// ```
98    #[staticmethod]
99    fn from_der(data: &[u8]) -> PyResult<Self> {
100        let inner =
101            BackendPublicKey::from_der(data).map_err(|e| PyValueError::new_err(format!("{e}")))?;
102        Ok(Self { inner })
103    }
104
105    /// Serialize this public key to PEM-encoded SubjectPublicKeyInfo.
106    ///
107    /// ```python,ignore
108    /// pem = pub.to_pem()
109    /// open("pubkey.pem", "wb").write(pem)
110    /// ```
111    fn to_pem<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
112        let pem = self
113            .inner
114            .to_pem()
115            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
116        Ok(PyBytes::new(py, &pem))
117    }
118
119    /// Serialize this public key to DER-encoded SubjectPublicKeyInfo.
120    ///
121    /// ```python,ignore
122    /// der = pub.to_der()
123    /// open("pubkey.der", "wb").write(der)
124    /// ```
125    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
126        let der = self
127            .inner
128            .to_der()
129            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
130        Ok(PyBytes::new(py, &der))
131    }
132
133    /// The key algorithm as a lowercase string.
134    ///
135    /// Returns one of ``"rsa"``, ``"ec"``, ``"ed25519"``, ``"ed448"``,
136    /// ``"dsa"``, or ``"unknown"``.
137    #[getter]
138    fn key_type(&self) -> &'static str {
139        self.inner.key_type()
140    }
141
142    /// The key size in bits, or ``None`` for EdDSA keys.
143    ///
144    /// For RSA this is the modulus bit-length; for EC this is the field
145    /// bit-length.  Returns ``None`` for Ed25519 and Ed448.
146    #[getter]
147    fn key_size(&self) -> Option<i64> {
148        self.inner.key_bit_size()
149    }
150
151    /// The RSA modulus ``n`` as big-endian bytes, or ``None`` for non-RSA keys.
152    #[getter]
153    fn modulus<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
154        match self
155            .inner
156            .rsa_modulus()
157            .map_err(|e| PyValueError::new_err(format!("{e}")))?
158        {
159            Some(n) => Ok(Some(PyBytes::new(py, &n))),
160            None => Ok(None),
161        }
162    }
163
164    /// The RSA public exponent ``e`` as big-endian bytes, or ``None`` for
165    /// non-RSA keys.
166    ///
167    /// The most common value is ``b'\x01\x00\x01'`` (65537).
168    #[getter]
169    fn public_exponent<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
170        match self
171            .inner
172            .rsa_public_exponent()
173            .map_err(|e| PyValueError::new_err(format!("{e}")))?
174        {
175            Some(e) => Ok(Some(PyBytes::new(py, &e))),
176            None => Ok(None),
177        }
178    }
179
180    /// The NIST curve name for EC keys, or ``None`` for non-EC keys.
181    ///
182    /// Returns ``"P-256"``, ``"P-384"``, ``"P-521"``, or ``"unknown"`` for
183    /// EC keys on other curves.
184    #[getter]
185    fn curve_name(&self) -> PyResult<Option<&'static str>> {
186        self.inner
187            .ec_curve_name()
188            .map_err(|e| PyValueError::new_err(format!("{e}")))
189    }
190
191    /// The affine X coordinate of the EC public key as big-endian bytes, or
192    /// ``None`` for non-EC keys.
193    #[getter]
194    fn x<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
195        match self
196            .inner
197            .ec_affine_coordinates()
198            .map_err(|e| PyValueError::new_err(format!("{e}")))?
199        {
200            Some((xv, _)) => Ok(Some(PyBytes::new(py, &xv))),
201            None => Ok(None),
202        }
203    }
204
205    /// The affine Y coordinate of the EC public key as big-endian bytes, or
206    /// ``None`` for non-EC keys.
207    #[getter]
208    fn y<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
209        match self
210            .inner
211            .ec_affine_coordinates()
212            .map_err(|e| PyValueError::new_err(format!("{e}")))?
213        {
214            Some((_, yv)) => Ok(Some(PyBytes::new(py, &yv))),
215            None => Ok(None),
216        }
217    }
218
219    /// Encrypt ``plaintext`` with RSA-OAEP using the specified hash algorithm.
220    ///
221    /// ``hash_algorithm`` must be one of ``"sha1"``, ``"sha224"``,
222    /// ``"sha256"``, ``"sha384"``, or ``"sha512"``.
223    ///
224    /// Raises :exc:`ValueError` if this key is not an RSA key.
225    ///
226    /// ```python,ignore
227    /// ct = pub.rsa_oaep_encrypt(b"secret data", "sha256")
228    /// ```
229    #[pyo3(signature = (plaintext, hash_algorithm = "sha256"))]
230    fn rsa_oaep_encrypt<'py>(
231        &self,
232        py: Python<'py>,
233        plaintext: &[u8],
234        hash_algorithm: &str,
235    ) -> PyResult<Bound<'py, PyBytes>> {
236        use synta_certificate::OpensslRsaOaepEncryptor;
237        let ct = OpensslRsaOaepEncryptor::new(self.inner.spki_der(), hash_algorithm)
238            .map_err(|e| PyValueError::new_err(format!("{e}")))?
239            .encrypt_key(plaintext)
240            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
241        Ok(PyBytes::new(py, &ct))
242    }
243
244    /// Encrypt ``plaintext`` with RSA PKCS\#1 v1.5 padding.
245    ///
246    /// Raises :exc:`ValueError` if this key is not an RSA key.
247    ///
248    /// ```python,ignore
249    /// ct = pub.rsa_pkcs1v15_encrypt(b"secret data")
250    /// ```
251    fn rsa_pkcs1v15_encrypt<'py>(
252        &self,
253        py: Python<'py>,
254        plaintext: &[u8],
255    ) -> PyResult<Bound<'py, PyBytes>> {
256        use synta_certificate::OpensslRsaPkcs1Encryptor;
257        let ct = OpensslRsaPkcs1Encryptor::new(self.inner.spki_der())
258            .map_err(|e| PyValueError::new_err(format!("{e}")))?
259            .encrypt_key(plaintext)
260            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
261        Ok(PyBytes::new(py, &ct))
262    }
263
264    /// Verify a signature over ``data``.
265    ///
266    /// ``algorithm`` is the hash algorithm used during signing.  It must be one
267    /// of ``"sha1"``, ``"sha224"``, ``"sha256"``, ``"sha384"``, or
268    /// ``"sha512"`` for RSA (PKCS\#1 v1.5) and ECDSA keys.  For Ed25519 and
269    /// Ed448 keys pass ``None`` (or omit the argument) — no pre-hash is used.
270    ///
271    /// Raises :exc:`ValueError` if the signature is invalid or the algorithm
272    /// combination is unsupported.
273    ///
274    /// ```python,ignore
275    /// pub.verify_signature(sig, data, "sha256")   # RSA or ECDSA
276    /// ed_pub.verify_signature(sig, data)          # Ed25519 / Ed448
277    /// ```
278    #[pyo3(signature = (signature, data, algorithm = None))]
279    fn verify_signature(
280        &self,
281        signature: &[u8],
282        data: &[u8],
283        algorithm: Option<&str>,
284    ) -> PyResult<()> {
285        self.inner
286            .verify_message(data, signature, algorithm)
287            .map_err(|e| PyValueError::new_err(format!("{e}")))
288    }
289
290    fn __repr__(&self) -> String {
291        let kt = self.inner.key_type();
292        let bits = match kt {
293            "ed25519" | "ed448" => String::new(),
294            _ => self
295                .inner
296                .key_bit_size()
297                .map(|b| format!(", key_size={b}"))
298                .unwrap_or_default(),
299        };
300        format!("PublicKey(key_type={kt:?}{bits})")
301    }
302}
303
304// ── PrivateKey ────────────────────────────────────────────────────────────────
305
306/// An asymmetric private key.
307///
308/// Supports RSA, EC (P-256, P-384, P-521), Ed25519, Ed448, and DSA keys.
309/// Load from PEM (optionally password-protected) or unencrypted PKCS\#8 DER;
310/// serialize back to PEM (optionally encrypted with AES-256-CBC) or
311/// unencrypted PKCS\#8 DER.  RSA keys can decrypt ciphertext with OAEP or
312/// PKCS\#1 v1.5 padding.
313///
314/// ```python,ignore
315/// import synta
316///
317/// # Load an encrypted RSA private key from PEM:
318/// with open("rsa_key.pem", "rb") as f:
319///     priv = synta.PrivateKey.from_pem(f.read(), password=b"secret")
320///
321/// # Extract the public key:
322/// pub = priv.public_key
323///
324/// # Decrypt RSA-OAEP ciphertext:
325/// plaintext = priv.rsa_oaep_decrypt(ciphertext, "sha256")
326/// ```
327#[pyclass(frozen, name = "PrivateKey")]
328pub struct PyPrivateKey {
329    pub(crate) inner: BackendPrivateKey,
330}
331
332#[pymethods]
333impl PyPrivateKey {
334    /// Load a private key from PEM-encoded data.
335    ///
336    /// Supports RSA, EC, Ed25519, Ed448, and DSA keys in both PKCS\#8
337    /// (``-----BEGIN PRIVATE KEY-----``) and traditional
338    /// (``-----BEGIN RSA PRIVATE KEY-----`` etc.) PEM formats.
339    ///
340    /// If the PEM block is password-protected, pass the passphrase as
341    /// ``password``.
342    ///
343    /// ```python,ignore
344    /// # Unencrypted key:
345    /// priv = synta.PrivateKey.from_pem(open("key.pem", "rb").read())
346    ///
347    /// # Encrypted key:
348    /// priv = synta.PrivateKey.from_pem(open("key.pem", "rb").read(), password=b"pass")
349    /// ```
350    #[staticmethod]
351    #[pyo3(signature = (data, password = None))]
352    fn from_pem(data: &[u8], password: Option<&[u8]>) -> PyResult<Self> {
353        let inner = BackendPrivateKey::from_pem(data, password)
354            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
355        Ok(Self { inner })
356    }
357
358    /// Load an unencrypted private key from PKCS\#8 DER bytes.
359    ///
360    /// ```python,ignore
361    /// with open("key.der", "rb") as f:
362    ///     priv = synta.PrivateKey.from_der(f.read())
363    /// ```
364    #[staticmethod]
365    fn from_der(data: &[u8]) -> PyResult<Self> {
366        let inner =
367            BackendPrivateKey::from_der(data).map_err(|e| PyValueError::new_err(format!("{e}")))?;
368        Ok(Self { inner })
369    }
370
371    /// Serialize this private key to PEM-encoded PKCS\#8.
372    ///
373    /// If ``password`` is provided the output is encrypted with AES-256-CBC.
374    ///
375    /// ```python,ignore
376    /// # Unencrypted:
377    /// pem = priv.to_pem()
378    ///
379    /// # Encrypted:
380    /// pem = priv.to_pem(password=b"my-passphrase")
381    /// ```
382    #[pyo3(signature = (password = None))]
383    fn to_pem<'py>(
384        &self,
385        py: Python<'py>,
386        password: Option<&[u8]>,
387    ) -> PyResult<Bound<'py, PyBytes>> {
388        let pem = self
389            .inner
390            .to_pem(password)
391            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
392        Ok(PyBytes::new(py, &pem))
393    }
394
395    /// Serialize this private key to unencrypted PKCS\#8 DER.
396    ///
397    /// ```python,ignore
398    /// der = priv.to_der()
399    /// open("key.der", "wb").write(der)
400    /// ```
401    fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
402        let der = self
403            .inner
404            .to_der()
405            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
406        Ok(PyBytes::new(py, &der))
407    }
408
409    /// Serialize this private key to encrypted PKCS\#8 DER
410    /// (``EncryptedPrivateKeyInfo``, RFC 5958 §3).
411    ///
412    /// ```python,ignore
413    /// der = priv.to_pkcs8_encrypted(b"my-passphrase")
414    /// open("key.p8e", "wb").write(der)
415    ///
416    /// # Round-trip:
417    /// priv2 = synta.PrivateKey.from_pkcs8_encrypted(der, b"my-passphrase")
418    /// assert priv2.to_der() == priv.to_der()
419    /// ```
420    fn to_pkcs8_encrypted<'py>(
421        &self,
422        py: Python<'py>,
423        password: &[u8],
424    ) -> PyResult<Bound<'py, PyBytes>> {
425        let der = self
426            .inner
427            .to_pkcs8_encrypted(password)
428            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
429        Ok(PyBytes::new(py, &der))
430    }
431
432    /// Load a private key from an encrypted PKCS\#8 DER blob
433    /// (``EncryptedPrivateKeyInfo``).
434    ///
435    /// ```python,ignore
436    /// der = open("key.p8e", "rb").read()
437    /// priv = synta.PrivateKey.from_pkcs8_encrypted(der, b"my-passphrase")
438    /// ```
439    #[staticmethod]
440    fn from_pkcs8_encrypted(data: &[u8], password: &[u8]) -> PyResult<Self> {
441        let inner = BackendPrivateKey::from_pkcs8_encrypted(data, password)
442            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
443        Ok(Self { inner })
444    }
445
446    /// The key algorithm as a lowercase string.
447    ///
448    /// Returns one of ``"rsa"``, ``"ec"``, ``"ed25519"``, ``"ed448"``,
449    /// ``"dsa"``, or ``"unknown"``.
450    #[getter]
451    fn key_type(&self) -> &'static str {
452        self.inner.key_type()
453    }
454
455    /// The key size in bits, or ``None`` for EdDSA keys.
456    #[getter]
457    fn key_size(&self) -> Option<i64> {
458        self.inner.key_bit_size()
459    }
460
461    /// The public key corresponding to this private key.
462    ///
463    /// ```python,ignore
464    /// pub = priv.public_key
465    /// ct = pub.rsa_oaep_encrypt(b"data", "sha256")
466    /// ```
467    #[getter]
468    fn public_key(&self) -> PyResult<PyPublicKey> {
469        let bpk = self
470            .inner
471            .public_key()
472            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
473        Ok(PyPublicKey { inner: bpk })
474    }
475
476    /// Decrypt ``ciphertext`` with RSA-OAEP using the specified hash algorithm.
477    ///
478    /// ``hash_algorithm`` must be one of ``"sha1"``, ``"sha224"``,
479    /// ``"sha256"``, ``"sha384"``, or ``"sha512"``.
480    ///
481    /// Raises :exc:`ValueError` if this key is not an RSA key.
482    ///
483    /// ```python,ignore
484    /// plaintext = priv.rsa_oaep_decrypt(ciphertext, "sha256")
485    /// ```
486    #[pyo3(signature = (ciphertext, hash_algorithm = "sha256"))]
487    fn rsa_oaep_decrypt<'py>(
488        &self,
489        py: Python<'py>,
490        ciphertext: &[u8],
491        hash_algorithm: &str,
492    ) -> PyResult<Bound<'py, PyBytes>> {
493        use synta_certificate::OpensslRsaOaepDecryptor;
494        let pkcs8 = self
495            .inner
496            .to_der()
497            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
498        let pt = OpensslRsaOaepDecryptor::new(&pkcs8, hash_algorithm)
499            .map_err(|e| PyValueError::new_err(format!("{e}")))?
500            .decrypt_key(ciphertext)
501            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
502        Ok(PyBytes::new(py, &pt))
503    }
504
505    /// Decrypt ``ciphertext`` with RSA PKCS\#1 v1.5 padding.
506    ///
507    /// Raises :exc:`ValueError` if this key is not an RSA key.
508    ///
509    /// ```python,ignore
510    /// plaintext = priv.rsa_pkcs1v15_decrypt(ciphertext)
511    /// ```
512    fn rsa_pkcs1v15_decrypt<'py>(
513        &self,
514        py: Python<'py>,
515        ciphertext: &[u8],
516    ) -> PyResult<Bound<'py, PyBytes>> {
517        use synta_certificate::OpensslRsaPkcs1Decryptor;
518        let pkcs8 = self
519            .inner
520            .to_der()
521            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
522        let pt = OpensslRsaPkcs1Decryptor::new(&pkcs8)
523            .map_err(|e| PyValueError::new_err(format!("{e}")))?
524            .decrypt_key(ciphertext)
525            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
526        Ok(PyBytes::new(py, &pt))
527    }
528
529    /// Generate a new RSA private key.
530    ///
531    /// ``key_size`` is the modulus bit-length (e.g. 2048, 3072, 4096).
532    /// ``public_exponent`` defaults to 65537.
533    ///
534    /// ```python,ignore
535    /// priv = synta.PrivateKey.generate_rsa(2048)
536    /// ```
537    #[staticmethod]
538    #[pyo3(signature = (key_size, public_exponent = 65537))]
539    fn generate_rsa(key_size: u32, public_exponent: u32) -> PyResult<Self> {
540        let inner = BackendPrivateKey::generate_rsa(key_size, public_exponent)
541            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
542        Ok(Self { inner })
543    }
544
545    /// Generate a new EC private key on the specified named curve.
546    ///
547    /// ``curve`` must be one of ``"P-256"``, ``"P-384"``, or ``"P-521"``.
548    /// Raises :exc:`ValueError` for unknown curve names.
549    ///
550    /// ```python,ignore
551    /// priv = synta.PrivateKey.generate_ec("P-256")
552    /// ```
553    #[staticmethod]
554    #[pyo3(signature = (curve = "P-256"))]
555    fn generate_ec(curve: &str) -> PyResult<Self> {
556        let inner = BackendPrivateKey::generate_ec(curve)
557            .map_err(|e| PyValueError::new_err(format!("{e}")))?;
558        Ok(Self { inner })
559    }
560
561    fn __repr__(&self) -> String {
562        let kt = self.inner.key_type();
563        let bits = match kt {
564            "ed25519" | "ed448" => String::new(),
565            _ => self
566                .inner
567                .key_bit_size()
568                .map(|b| format!(", key_size={b}"))
569                .unwrap_or_default(),
570        };
571        format!("PrivateKey(key_type={kt:?}{bits})")
572    }
573}
574
575// ── PrivateKey trait impl ─────────────────────────────────────────────────────
576
577/// Implement the backend-agnostic [`synta_certificate::PrivateKey`] trait for
578/// [`PyPrivateKey`] by delegating to [`synta_certificate::BackendPrivateKey`].
579///
580/// This allows Python binding code (e.g. `cert_builder.rs`) to call
581/// `key.as_signer(algorithm)` without importing backend-specific types.
582impl synta_certificate::PrivateKey for PyPrivateKey {
583    fn public_key_spki_der(&self) -> Result<Vec<u8>, synta_certificate::PrivateKeyError> {
584        self.inner.public_key_spki_der()
585    }
586
587    fn as_signer(&self, algorithm: &str) -> Box<dyn synta_certificate::ErasedCertificateSigner> {
588        self.inner.as_signer(algorithm)
589    }
590}