| //! Digital Signatures |
| //! |
| //! DSA ensures a message originated from a known sender, and was not modified. |
| //! DSA uses asymmetrical keys and an algorithm to output a signature of the message |
| //! using the private key that can be validated with the public key but not be generated |
| //! without the private key. |
| |
| use cfg_if::cfg_if; |
| use foreign_types::{ForeignType, ForeignTypeRef}; |
| use libc::c_int; |
| use std::fmt; |
| use std::mem; |
| use std::ptr; |
| |
| use crate::bn::{BigNum, BigNumRef}; |
| use crate::error::ErrorStack; |
| use crate::pkey::{HasParams, HasPrivate, HasPublic, Private, Public}; |
| use crate::util::ForeignTypeRefExt; |
| use crate::{cvt, cvt_p}; |
| use openssl_macros::corresponds; |
| |
| generic_foreign_type_and_impl_send_sync! { |
| type CType = ffi::DSA; |
| fn drop = ffi::DSA_free; |
| |
| /// Object representing DSA keys. |
| /// |
| /// A DSA object contains the parameters p, q, and g. There is a private |
| /// and public key. The values p, g, and q are: |
| /// |
| /// * `p`: DSA prime parameter |
| /// * `q`: DSA sub-prime parameter |
| /// * `g`: DSA base parameter |
| /// |
| /// These values are used to calculate a pair of asymmetrical keys used for |
| /// signing. |
| /// |
| /// OpenSSL documentation at [`DSA_new`] |
| /// |
| /// [`DSA_new`]: https://www.openssl.org/docs/man1.1.0/crypto/DSA_new.html |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use openssl::dsa::Dsa; |
| /// use openssl::error::ErrorStack; |
| /// use openssl::pkey::Private; |
| /// |
| /// fn create_dsa() -> Result<Dsa<Private>, ErrorStack> { |
| /// let sign = Dsa::generate(2048)?; |
| /// Ok(sign) |
| /// } |
| /// # fn main() { |
| /// # create_dsa(); |
| /// # } |
| /// ``` |
| pub struct Dsa<T>; |
| /// Reference to [`Dsa`]. |
| /// |
| /// [`Dsa`]: struct.Dsa.html |
| pub struct DsaRef<T>; |
| } |
| |
| impl<T> Clone for Dsa<T> { |
| fn clone(&self) -> Dsa<T> { |
| (**self).to_owned() |
| } |
| } |
| |
| impl<T> ToOwned for DsaRef<T> { |
| type Owned = Dsa<T>; |
| |
| fn to_owned(&self) -> Dsa<T> { |
| unsafe { |
| ffi::DSA_up_ref(self.as_ptr()); |
| Dsa::from_ptr(self.as_ptr()) |
| } |
| } |
| } |
| |
| impl<T> DsaRef<T> |
| where |
| T: HasPublic, |
| { |
| to_pem! { |
| /// Serializes the public key into a PEM-encoded SubjectPublicKeyInfo structure. |
| /// |
| /// The output will have a header of `-----BEGIN PUBLIC KEY-----`. |
| #[corresponds(PEM_write_bio_DSA_PUBKEY)] |
| public_key_to_pem, |
| ffi::PEM_write_bio_DSA_PUBKEY |
| } |
| |
| to_der! { |
| /// Serializes the public key into a DER-encoded SubjectPublicKeyInfo structure. |
| #[corresponds(i2d_DSA_PUBKEY)] |
| public_key_to_der, |
| ffi::i2d_DSA_PUBKEY |
| } |
| |
| /// Returns a reference to the public key component of `self`. |
| #[corresponds(DSA_get0_key)] |
| pub fn pub_key(&self) -> &BigNumRef { |
| unsafe { |
| let mut pub_key = ptr::null(); |
| DSA_get0_key(self.as_ptr(), &mut pub_key, ptr::null_mut()); |
| BigNumRef::from_const_ptr(pub_key) |
| } |
| } |
| } |
| |
| impl<T> DsaRef<T> |
| where |
| T: HasPrivate, |
| { |
| private_key_to_pem! { |
| /// Serializes the private key to a PEM-encoded DSAPrivateKey structure. |
| /// |
| /// The output will have a header of `-----BEGIN DSA PRIVATE KEY-----`. |
| #[corresponds(PEM_write_bio_DSAPrivateKey)] |
| private_key_to_pem, |
| /// Serializes the private key to a PEM-encoded encrypted DSAPrivateKey structure. |
| /// |
| /// The output will have a header of `-----BEGIN DSA PRIVATE KEY-----`. |
| #[corresponds(PEM_write_bio_DSAPrivateKey)] |
| private_key_to_pem_passphrase, |
| ffi::PEM_write_bio_DSAPrivateKey |
| } |
| |
| /// Returns a reference to the private key component of `self`. |
| #[corresponds(DSA_get0_key)] |
| pub fn priv_key(&self) -> &BigNumRef { |
| unsafe { |
| let mut priv_key = ptr::null(); |
| DSA_get0_key(self.as_ptr(), ptr::null_mut(), &mut priv_key); |
| BigNumRef::from_const_ptr(priv_key) |
| } |
| } |
| } |
| |
| impl<T> DsaRef<T> |
| where |
| T: HasParams, |
| { |
| /// Returns the maximum size of the signature output by `self` in bytes. |
| #[corresponds(DSA_size)] |
| pub fn size(&self) -> u32 { |
| unsafe { ffi::DSA_size(self.as_ptr()) as u32 } |
| } |
| |
| /// Returns the DSA prime parameter of `self`. |
| #[corresponds(DSA_get0_pqg)] |
| pub fn p(&self) -> &BigNumRef { |
| unsafe { |
| let mut p = ptr::null(); |
| DSA_get0_pqg(self.as_ptr(), &mut p, ptr::null_mut(), ptr::null_mut()); |
| BigNumRef::from_const_ptr(p) |
| } |
| } |
| |
| /// Returns the DSA sub-prime parameter of `self`. |
| #[corresponds(DSA_get0_pqg)] |
| pub fn q(&self) -> &BigNumRef { |
| unsafe { |
| let mut q = ptr::null(); |
| DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), &mut q, ptr::null_mut()); |
| BigNumRef::from_const_ptr(q) |
| } |
| } |
| |
| /// Returns the DSA base parameter of `self`. |
| #[corresponds(DSA_get0_pqg)] |
| pub fn g(&self) -> &BigNumRef { |
| unsafe { |
| let mut g = ptr::null(); |
| DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), ptr::null_mut(), &mut g); |
| BigNumRef::from_const_ptr(g) |
| } |
| } |
| } |
| #[cfg(boringssl)] |
| type BitType = libc::c_uint; |
| #[cfg(not(boringssl))] |
| type BitType = c_int; |
| |
| impl Dsa<Private> { |
| /// Generate a DSA key pair. |
| /// |
| /// Calls [`DSA_generate_parameters_ex`] to populate the `p`, `g`, and `q` values. |
| /// These values are used to generate the key pair with [`DSA_generate_key`]. |
| /// |
| /// The `bits` parameter corresponds to the length of the prime `p`. |
| /// |
| /// [`DSA_generate_parameters_ex`]: https://www.openssl.org/docs/man1.1.0/crypto/DSA_generate_parameters_ex.html |
| /// [`DSA_generate_key`]: https://www.openssl.org/docs/man1.1.0/crypto/DSA_generate_key.html |
| pub fn generate(bits: u32) -> Result<Dsa<Private>, ErrorStack> { |
| ffi::init(); |
| unsafe { |
| let dsa = Dsa::from_ptr(cvt_p(ffi::DSA_new())?); |
| cvt(ffi::DSA_generate_parameters_ex( |
| dsa.0, |
| bits as BitType, |
| ptr::null(), |
| 0, |
| ptr::null_mut(), |
| ptr::null_mut(), |
| ptr::null_mut(), |
| ))?; |
| cvt(ffi::DSA_generate_key(dsa.0))?; |
| Ok(dsa) |
| } |
| } |
| |
| /// Create a DSA key pair with the given parameters |
| /// |
| /// `p`, `q` and `g` are the common parameters. |
| /// `priv_key` is the private component of the key pair. |
| /// `pub_key` is the public component of the key. Can be computed via `g^(priv_key) mod p` |
| pub fn from_private_components( |
| p: BigNum, |
| q: BigNum, |
| g: BigNum, |
| priv_key: BigNum, |
| pub_key: BigNum, |
| ) -> Result<Dsa<Private>, ErrorStack> { |
| ffi::init(); |
| unsafe { |
| let dsa = Dsa::from_ptr(cvt_p(ffi::DSA_new())?); |
| cvt(DSA_set0_pqg(dsa.0, p.as_ptr(), q.as_ptr(), g.as_ptr()))?; |
| mem::forget((p, q, g)); |
| cvt(DSA_set0_key(dsa.0, pub_key.as_ptr(), priv_key.as_ptr()))?; |
| mem::forget((pub_key, priv_key)); |
| Ok(dsa) |
| } |
| } |
| } |
| |
| impl Dsa<Public> { |
| from_pem! { |
| /// Decodes a PEM-encoded SubjectPublicKeyInfo structure containing a DSA key. |
| /// |
| /// The input should have a header of `-----BEGIN PUBLIC KEY-----`. |
| #[corresponds(PEM_read_bio_DSA_PUBKEY)] |
| public_key_from_pem, |
| Dsa<Public>, |
| ffi::PEM_read_bio_DSA_PUBKEY |
| } |
| |
| from_der! { |
| /// Decodes a DER-encoded SubjectPublicKeyInfo structure containing a DSA key. |
| #[corresponds(d2i_DSA_PUBKEY)] |
| public_key_from_der, |
| Dsa<Public>, |
| ffi::d2i_DSA_PUBKEY |
| } |
| |
| /// Create a new DSA key with only public components. |
| /// |
| /// `p`, `q` and `g` are the common parameters. |
| /// `pub_key` is the public component of the key. |
| pub fn from_public_components( |
| p: BigNum, |
| q: BigNum, |
| g: BigNum, |
| pub_key: BigNum, |
| ) -> Result<Dsa<Public>, ErrorStack> { |
| ffi::init(); |
| unsafe { |
| let dsa = Dsa::from_ptr(cvt_p(ffi::DSA_new())?); |
| cvt(DSA_set0_pqg(dsa.0, p.as_ptr(), q.as_ptr(), g.as_ptr()))?; |
| mem::forget((p, q, g)); |
| cvt(DSA_set0_key(dsa.0, pub_key.as_ptr(), ptr::null_mut()))?; |
| mem::forget(pub_key); |
| Ok(dsa) |
| } |
| } |
| } |
| |
| impl<T> fmt::Debug for Dsa<T> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "DSA") |
| } |
| } |
| |
| cfg_if! { |
| if #[cfg(any(ossl110, libressl273))] { |
| use ffi::{DSA_get0_key, DSA_get0_pqg, DSA_set0_key, DSA_set0_pqg}; |
| } else { |
| #[allow(bad_style)] |
| unsafe fn DSA_get0_pqg( |
| d: *mut ffi::DSA, |
| p: *mut *const ffi::BIGNUM, |
| q: *mut *const ffi::BIGNUM, |
| g: *mut *const ffi::BIGNUM) |
| { |
| if !p.is_null() { |
| *p = (*d).p; |
| } |
| if !q.is_null() { |
| *q = (*d).q; |
| } |
| if !g.is_null() { |
| *g = (*d).g; |
| } |
| } |
| |
| #[allow(bad_style)] |
| unsafe fn DSA_get0_key( |
| d: *mut ffi::DSA, |
| pub_key: *mut *const ffi::BIGNUM, |
| priv_key: *mut *const ffi::BIGNUM) |
| { |
| if !pub_key.is_null() { |
| *pub_key = (*d).pub_key; |
| } |
| if !priv_key.is_null() { |
| *priv_key = (*d).priv_key; |
| } |
| } |
| |
| #[allow(bad_style)] |
| unsafe fn DSA_set0_key( |
| d: *mut ffi::DSA, |
| pub_key: *mut ffi::BIGNUM, |
| priv_key: *mut ffi::BIGNUM) -> c_int |
| { |
| (*d).pub_key = pub_key; |
| (*d).priv_key = priv_key; |
| 1 |
| } |
| |
| #[allow(bad_style)] |
| unsafe fn DSA_set0_pqg( |
| d: *mut ffi::DSA, |
| p: *mut ffi::BIGNUM, |
| q: *mut ffi::BIGNUM, |
| g: *mut ffi::BIGNUM) -> c_int |
| { |
| (*d).p = p; |
| (*d).q = q; |
| (*d).g = g; |
| 1 |
| } |
| } |
| } |
| |
| foreign_type_and_impl_send_sync! { |
| type CType = ffi::DSA_SIG; |
| fn drop = ffi::DSA_SIG_free; |
| |
| /// Object representing DSA signature. |
| /// |
| /// DSA signatures consist of two components: `r` and `s`. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use std::convert::TryInto; |
| /// |
| /// use openssl::bn::BigNum; |
| /// use openssl::dsa::{Dsa, DsaSig}; |
| /// use openssl::hash::MessageDigest; |
| /// use openssl::pkey::PKey; |
| /// use openssl::sign::{Signer, Verifier}; |
| /// |
| /// const TEST_DATA: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; |
| /// let dsa_ref = Dsa::generate(1024).unwrap(); |
| /// |
| /// let pub_key: PKey<_> = dsa_ref.clone().try_into().unwrap(); |
| /// let priv_key: PKey<_> = dsa_ref.try_into().unwrap(); |
| /// |
| /// let mut signer = if let Ok(signer) = Signer::new(MessageDigest::sha256(), &priv_key) { |
| /// signer |
| /// } else { |
| /// // DSA signing is not supported (eg. BoringSSL) |
| /// return; |
| /// }; |
| /// |
| /// signer.update(TEST_DATA).unwrap(); |
| /// |
| /// let signature = signer.sign_to_vec().unwrap(); |
| /// // Parse DER-encoded DSA signature |
| /// let signature = DsaSig::from_der(&signature).unwrap(); |
| /// |
| /// // Extract components `r` and `s` |
| /// let r = BigNum::from_slice(&signature.r().to_vec()).unwrap(); |
| /// let s = BigNum::from_slice(&signature.s().to_vec()).unwrap(); |
| /// |
| /// // Construct new DSA signature from components |
| /// let signature = DsaSig::from_private_components(r, s).unwrap(); |
| /// |
| /// // Serialize DSA signature to DER |
| /// let signature = signature.to_der().unwrap(); |
| /// |
| /// let mut verifier = Verifier::new(MessageDigest::sha256(), &pub_key).unwrap(); |
| /// verifier.update(TEST_DATA).unwrap(); |
| /// assert!(verifier.verify(&signature[..]).unwrap()); |
| /// ``` |
| pub struct DsaSig; |
| |
| /// Reference to a [`DsaSig`]. |
| pub struct DsaSigRef; |
| } |
| |
| impl DsaSig { |
| /// Returns a new `DsaSig` by setting the `r` and `s` values associated with an DSA signature. |
| #[corresponds(DSA_SIG_set0)] |
| pub fn from_private_components(r: BigNum, s: BigNum) -> Result<Self, ErrorStack> { |
| unsafe { |
| let sig = cvt_p(ffi::DSA_SIG_new())?; |
| DSA_SIG_set0(sig, r.as_ptr(), s.as_ptr()); |
| mem::forget((r, s)); |
| Ok(DsaSig::from_ptr(sig)) |
| } |
| } |
| |
| from_der! { |
| /// Decodes a DER-encoded DSA signature. |
| #[corresponds(d2i_DSA_SIG)] |
| from_der, |
| DsaSig, |
| ffi::d2i_DSA_SIG |
| } |
| } |
| |
| impl fmt::Debug for DsaSig { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.debug_struct("DsaSig") |
| .field("r", self.r()) |
| .field("s", self.s()) |
| .finish() |
| } |
| } |
| |
| impl DsaSigRef { |
| to_der! { |
| /// Serializes the DSA signature into a DER-encoded `DSASignature` structure. |
| #[corresponds(i2d_DSA_SIG)] |
| to_der, |
| ffi::i2d_DSA_SIG |
| } |
| |
| /// Returns internal component `r` of an `DsaSig`. |
| #[corresponds(DSA_SIG_get0)] |
| pub fn r(&self) -> &BigNumRef { |
| unsafe { |
| let mut r = ptr::null(); |
| DSA_SIG_get0(self.as_ptr(), &mut r, ptr::null_mut()); |
| BigNumRef::from_const_ptr(r) |
| } |
| } |
| |
| /// Returns internal component `s` of an `DsaSig`. |
| #[corresponds(DSA_SIG_get0)] |
| pub fn s(&self) -> &BigNumRef { |
| unsafe { |
| let mut s = ptr::null(); |
| DSA_SIG_get0(self.as_ptr(), ptr::null_mut(), &mut s); |
| BigNumRef::from_const_ptr(s) |
| } |
| } |
| } |
| |
| cfg_if! { |
| if #[cfg(any(ossl110, libressl273))] { |
| use ffi::{DSA_SIG_set0, DSA_SIG_get0}; |
| } else { |
| #[allow(bad_style)] |
| unsafe fn DSA_SIG_set0( |
| sig: *mut ffi::DSA_SIG, |
| r: *mut ffi::BIGNUM, |
| s: *mut ffi::BIGNUM, |
| ) -> c_int { |
| if r.is_null() || s.is_null() { |
| return 0; |
| } |
| ffi::BN_clear_free((*sig).r); |
| ffi::BN_clear_free((*sig).s); |
| (*sig).r = r; |
| (*sig).s = s; |
| 1 |
| } |
| |
| #[allow(bad_style)] |
| unsafe fn DSA_SIG_get0( |
| sig: *const ffi::DSA_SIG, |
| pr: *mut *const ffi::BIGNUM, |
| ps: *mut *const ffi::BIGNUM) |
| { |
| if !pr.is_null() { |
| (*pr) = (*sig).r; |
| } |
| if !ps.is_null() { |
| (*ps) = (*sig).s; |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::bn::BigNumContext; |
| #[cfg(not(boringssl))] |
| use crate::hash::MessageDigest; |
| #[cfg(not(boringssl))] |
| use crate::pkey::PKey; |
| #[cfg(not(boringssl))] |
| use crate::sign::{Signer, Verifier}; |
| |
| #[test] |
| pub fn test_generate() { |
| Dsa::generate(1024).unwrap(); |
| } |
| |
| #[test] |
| fn test_pubkey_generation() { |
| let dsa = Dsa::generate(1024).unwrap(); |
| let p = dsa.p(); |
| let g = dsa.g(); |
| let priv_key = dsa.priv_key(); |
| let pub_key = dsa.pub_key(); |
| let mut ctx = BigNumContext::new().unwrap(); |
| let mut calc = BigNum::new().unwrap(); |
| calc.mod_exp(g, priv_key, p, &mut ctx).unwrap(); |
| assert_eq!(&calc, pub_key) |
| } |
| |
| #[test] |
| fn test_priv_key_from_parts() { |
| let p = BigNum::from_u32(283).unwrap(); |
| let q = BigNum::from_u32(47).unwrap(); |
| let g = BigNum::from_u32(60).unwrap(); |
| let priv_key = BigNum::from_u32(15).unwrap(); |
| let pub_key = BigNum::from_u32(207).unwrap(); |
| |
| let dsa = Dsa::from_private_components(p, q, g, priv_key, pub_key).unwrap(); |
| assert_eq!(dsa.pub_key(), &BigNum::from_u32(207).unwrap()); |
| assert_eq!(dsa.priv_key(), &BigNum::from_u32(15).unwrap()); |
| assert_eq!(dsa.p(), &BigNum::from_u32(283).unwrap()); |
| assert_eq!(dsa.q(), &BigNum::from_u32(47).unwrap()); |
| assert_eq!(dsa.g(), &BigNum::from_u32(60).unwrap()); |
| } |
| |
| #[test] |
| fn test_pub_key_from_parts() { |
| let p = BigNum::from_u32(283).unwrap(); |
| let q = BigNum::from_u32(47).unwrap(); |
| let g = BigNum::from_u32(60).unwrap(); |
| let pub_key = BigNum::from_u32(207).unwrap(); |
| |
| let dsa = Dsa::from_public_components(p, q, g, pub_key).unwrap(); |
| assert_eq!(dsa.pub_key(), &BigNum::from_u32(207).unwrap()); |
| assert_eq!(dsa.p(), &BigNum::from_u32(283).unwrap()); |
| assert_eq!(dsa.q(), &BigNum::from_u32(47).unwrap()); |
| assert_eq!(dsa.g(), &BigNum::from_u32(60).unwrap()); |
| } |
| |
| #[test] |
| #[cfg(not(boringssl))] |
| fn test_signature() { |
| const TEST_DATA: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; |
| let dsa_ref = Dsa::generate(1024).unwrap(); |
| |
| let p = dsa_ref.p(); |
| let q = dsa_ref.q(); |
| let g = dsa_ref.g(); |
| |
| let pub_key = dsa_ref.pub_key(); |
| let priv_key = dsa_ref.priv_key(); |
| |
| let priv_key = Dsa::from_private_components( |
| BigNumRef::to_owned(p).unwrap(), |
| BigNumRef::to_owned(q).unwrap(), |
| BigNumRef::to_owned(g).unwrap(), |
| BigNumRef::to_owned(priv_key).unwrap(), |
| BigNumRef::to_owned(pub_key).unwrap(), |
| ) |
| .unwrap(); |
| let priv_key = PKey::from_dsa(priv_key).unwrap(); |
| |
| let pub_key = Dsa::from_public_components( |
| BigNumRef::to_owned(p).unwrap(), |
| BigNumRef::to_owned(q).unwrap(), |
| BigNumRef::to_owned(g).unwrap(), |
| BigNumRef::to_owned(pub_key).unwrap(), |
| ) |
| .unwrap(); |
| let pub_key = PKey::from_dsa(pub_key).unwrap(); |
| |
| let mut signer = Signer::new(MessageDigest::sha256(), &priv_key).unwrap(); |
| signer.update(TEST_DATA).unwrap(); |
| |
| let signature = signer.sign_to_vec().unwrap(); |
| let mut verifier = Verifier::new(MessageDigest::sha256(), &pub_key).unwrap(); |
| verifier.update(TEST_DATA).unwrap(); |
| assert!(verifier.verify(&signature[..]).unwrap()); |
| } |
| |
| #[test] |
| #[cfg(not(boringssl))] |
| fn test_signature_der() { |
| use std::convert::TryInto; |
| |
| const TEST_DATA: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; |
| let dsa_ref = Dsa::generate(1024).unwrap(); |
| |
| let pub_key: PKey<_> = dsa_ref.clone().try_into().unwrap(); |
| let priv_key: PKey<_> = dsa_ref.try_into().unwrap(); |
| |
| let mut signer = Signer::new(MessageDigest::sha256(), &priv_key).unwrap(); |
| signer.update(TEST_DATA).unwrap(); |
| |
| let signature = signer.sign_to_vec().unwrap(); |
| eprintln!("{:?}", signature); |
| let signature = DsaSig::from_der(&signature).unwrap(); |
| |
| let r = BigNum::from_slice(&signature.r().to_vec()).unwrap(); |
| let s = BigNum::from_slice(&signature.s().to_vec()).unwrap(); |
| |
| let signature = DsaSig::from_private_components(r, s).unwrap(); |
| let signature = signature.to_der().unwrap(); |
| |
| let mut verifier = Verifier::new(MessageDigest::sha256(), &pub_key).unwrap(); |
| verifier.update(TEST_DATA).unwrap(); |
| assert!(verifier.verify(&signature[..]).unwrap()); |
| } |
| |
| #[test] |
| #[allow(clippy::redundant_clone)] |
| fn clone() { |
| let key = Dsa::generate(2048).unwrap(); |
| drop(key.clone()); |
| } |
| |
| #[test] |
| fn dsa_sig_debug() { |
| let sig = DsaSig::from_der(&[ |
| 48, 46, 2, 21, 0, 135, 169, 24, 58, 153, 37, 175, 248, 200, 45, 251, 112, 238, 238, 89, |
| 172, 177, 182, 166, 237, 2, 21, 0, 159, 146, 151, 237, 187, 8, 82, 115, 14, 183, 103, |
| 12, 203, 46, 161, 208, 251, 167, 123, 131, |
| ]) |
| .unwrap(); |
| let s = format!("{:?}", sig); |
| assert_eq!(s, "DsaSig { r: 774484690634577222213819810519929266740561094381, s: 910998676210681457251421818099943952372231273347 }"); |
| } |
| } |