diff --git a/Cargo.lock b/Cargo.lock index 68d4bbea..90da2464 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1136,8 +1136,7 @@ dependencies = [ [[package]] name = "signature" version = "3.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39195ff4c0dc41c93e123825ca1f0d11b856df8b26d5fe140a522355632c4345" +source = "git+https://github.com/RustCrypto/traits?rev=77c001c5abdfea97c7710173c196c76253490734#77c001c5abdfea97c7710173c196c76253490734" dependencies = [ "digest", "rand_core 0.9.3", diff --git a/Cargo.toml b/Cargo.toml index 79c5444c..d18c18dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,6 @@ lms-signature = { path = "./lms" } ml-dsa = { path = "./ml-dsa" } rfc6979 = { path = "./rfc6979" } slh-dsa = { path = "./slh-dsa" } + +# https://github.com/RustCrypto/traits/pull/2004 +signature = { git = "https://github.com/RustCrypto/traits", rev = "77c001c5abdfea97c7710173c196c76253490734" } diff --git a/dsa/examples/sign.rs b/dsa/examples/sign.rs index 7e03ecb4..87eeb2f3 100644 --- a/dsa/examples/sign.rs +++ b/dsa/examples/sign.rs @@ -11,8 +11,9 @@ fn main() { let signing_key = SigningKey::generate(&mut rng, components); let verifying_key = signing_key.verifying_key(); - let signature = signing_key - .sign_digest_with_rng(&mut rand::rng(), Sha1::new().chain_update(b"hello world")); + let signature = signing_key.sign_digest_with_rng(&mut rand::rng(), |digest: &mut Sha1| { + digest.update(b"hello world") + }); let signing_key_bytes = signing_key.to_pkcs8_pem(LineEnding::LF).unwrap(); let verifying_key_bytes = verifying_key.to_public_key_pem(LineEnding::LF).unwrap(); diff --git a/dsa/src/signing_key.rs b/dsa/src/signing_key.rs index 4c8223bd..09df9245 100644 --- a/dsa/src/signing_key.rs +++ b/dsa/src/signing_key.rs @@ -13,7 +13,7 @@ use crypto_bigint::{ BoxedUint, NonZero, Resize, modular::{BoxedMontyForm, BoxedMontyParams}, }; -use digest::{Digest, FixedOutputReset, block_api::BlockSizeUser}; +use digest::{Digest, FixedOutputReset, Update, block_api::BlockSizeUser}; use signature::{ DigestSigner, MultipartSigner, RandomizedDigestSigner, Signer, hazmat::{PrehashSigner, RandomizedPrehashSigner}, @@ -157,9 +157,10 @@ impl Signer for SigningKey { impl MultipartSigner for SigningKey { fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result { - let mut digest = sha2::Sha256::new(); - msg.iter().for_each(|slice| digest.update(slice)); - self.try_sign_digest(digest) + self.try_sign_digest(|digest: &mut sha2::Sha256| { + msg.iter().for_each(|slice| Digest::update(digest, slice)); + Ok(()) + }) } } @@ -191,7 +192,12 @@ impl DigestSigner for SigningKey where D: Digest + BlockSizeUser + FixedOutputReset, { - fn try_sign_digest(&self, digest: D) -> Result { + fn try_sign_digest Result<(), signature::Error>>( + &self, + f: F, + ) -> Result { + let mut digest = D::new(); + f(&mut digest)?; let hash = digest.finalize_fixed(); let ks = crate::generate::secret_number_rfc6979::(self, &hash)?; @@ -201,15 +207,20 @@ where impl RandomizedDigestSigner for SigningKey where - D: Digest, + D: Digest + Update, { - fn try_sign_digest_with_rng( + fn try_sign_digest_with_rng< + R: TryCryptoRng + ?Sized, + F: Fn(&mut D) -> Result<(), signature::Error>, + >( &self, rng: &mut R, - digest: D, + f: F, ) -> Result { let ks = crate::generate::secret_number(rng, self.verifying_key().components())? .ok_or_else(signature::Error::new)?; + let mut digest = D::new(); + f(&mut digest)?; let hash = digest.finalize(); self.sign_prehashed(ks, &hash) diff --git a/dsa/src/verifying_key.rs b/dsa/src/verifying_key.rs index ef8dca8d..513aa37b 100644 --- a/dsa/src/verifying_key.rs +++ b/dsa/src/verifying_key.rs @@ -8,7 +8,7 @@ use crypto_bigint::{ BoxedUint, NonZero, Resize, modular::{BoxedMontyForm, BoxedMontyParams}, }; -use digest::Digest; +use digest::{Digest, Update}; use signature::{DigestVerifier, MultipartVerifier, Verifier, hazmat::PrehashVerifier}; #[cfg(feature = "pkcs8")] @@ -124,9 +124,13 @@ impl MultipartVerifier for VerifyingKey { msg: &[&[u8]], signature: &Signature, ) -> Result<(), signature::Error> { - let mut digest = sha2::Sha256::new(); - msg.iter().for_each(|slice| digest.update(slice)); - self.verify_digest(digest, signature) + self.verify_digest( + |digest: &mut sha2::Sha256| { + msg.iter().for_each(|slice| Digest::update(digest, slice)); + Ok(()) + }, + signature, + ) } } @@ -146,9 +150,15 @@ impl PrehashVerifier for VerifyingKey { impl DigestVerifier for VerifyingKey where - D: Digest, + D: Digest + Update, { - fn verify_digest(&self, digest: D, signature: &Signature) -> Result<(), signature::Error> { + fn verify_digest Result<(), signature::Error>>( + &self, + f: F, + signature: &Signature, + ) -> Result<(), signature::Error> { + let mut digest = D::new(); + f(&mut digest)?; let hash = digest.finalize(); let is_valid = self diff --git a/dsa/tests/deterministic.rs b/dsa/tests/deterministic.rs index 21194a3c..8995007a 100644 --- a/dsa/tests/deterministic.rs +++ b/dsa/tests/deterministic.rs @@ -102,7 +102,7 @@ fn generate_signature(signing_key: SigningKey, data: &[u8]) -> Signature where D: Digest + BlockSizeUser + FixedOutputReset, { - signing_key.sign_digest(::new().chain_update(data)) + signing_key.sign_digest(|digest: &mut D| Digest::update(digest, data)) } /// Generate a signature using the 1024-bit DSA key diff --git a/dsa/tests/signature.rs b/dsa/tests/signature.rs index dc805db8..e093a49c 100644 --- a/dsa/tests/signature.rs +++ b/dsa/tests/signature.rs @@ -71,8 +71,10 @@ fn decode_encode_signature() { #[test] fn sign_message() { let signing_key = generate_deterministic_keypair(); - let generated_signature = - signing_key.sign_digest_with_rng(&mut seeded_csprng(), Sha256::new().chain_update(MESSAGE)); + let generated_signature = signing_key + .sign_digest_with_rng(&mut seeded_csprng(), |digest: &mut Sha256| { + digest.update(MESSAGE) + }); let expected_signature = Signature::from_der(MESSAGE_SIGNATURE_CRATE_ASN1).expect("Failed to decode signature"); @@ -90,7 +92,13 @@ fn verify_signature() { assert!( verifying_key - .verify_digest(Sha256::new().chain_update(MESSAGE), &signature) + .verify_digest( + |digest: &mut Sha256| { + digest.update(MESSAGE); + Ok(()) + }, + &signature + ) .is_ok() ); } @@ -160,6 +168,12 @@ fn verify_signature_precision() { let signature = Signature::from_der(&asn1) .expect("Failed to parse ASN.1 representation of the test signature"); - let _ = verifying_key.verify_digest(Sha256::new().chain_update(MESSAGE), &signature); + let _ = verifying_key.verify_digest( + |digest: &mut Sha256| { + digest.update(MESSAGE); + Ok(()) + }, + &signature, + ); } } diff --git a/dsa/tests/signing_key.rs b/dsa/tests/signing_key.rs index 72ef4188..1f5cf9a3 100644 --- a/dsa/tests/signing_key.rs +++ b/dsa/tests/signing_key.rs @@ -49,12 +49,20 @@ fn sign_and_verify() { let signing_key = generate_keypair(); let verifying_key = signing_key.verifying_key(); - let signature = - signing_key.sign_digest_with_rng(&mut rand::thread_rng(), Sha1::new().chain_update(DATA)); + let signature = signing_key + .sign_digest_with_rng(&mut rand::thread_rng(), |digest: &mut Sha1| { + digest.update(DATA) + }); assert!( verifying_key - .verify_digest(Sha1::new().chain_update(DATA), &signature) + .verify_digest( + |digest: &mut Sha1| { + digest.update(DATA); + Ok(()) + }, + &signature + ) .is_ok() ); } diff --git a/ecdsa/src/recovery.rs b/ecdsa/src/recovery.rs index 4954eb2e..4009c160 100644 --- a/ecdsa/src/recovery.rs +++ b/ecdsa/src/recovery.rs @@ -8,7 +8,7 @@ use { elliptic_curve::{FieldBytes, subtle::CtOption}, signature::{ DigestSigner, MultipartSigner, RandomizedDigestSigner, Signer, - digest::FixedOutput, + digest::{FixedOutput, Update}, hazmat::{PrehashSigner, RandomizedPrehashSigner}, rand_core::TryCryptoRng, }, @@ -229,12 +229,17 @@ where impl DigestSigner, RecoveryId)> for SigningKey where C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, - D: Digest, + D: Digest + Update, Scalar: Invert>>, SignatureSize: ArraySize, { - fn try_sign_digest(&self, msg_digest: D) -> Result<(Signature, RecoveryId)> { - self.sign_digest_recoverable(msg_digest) + fn try_sign_digest Result<()>>( + &self, + f: F, + ) -> Result<(Signature, RecoveryId)> { + let mut digest = D::new(); + f(&mut digest)?; + self.sign_digest_recoverable(digest) } } @@ -262,12 +267,14 @@ where Scalar: Invert>>, SignatureSize: ArraySize, { - fn try_sign_digest_with_rng( + fn try_sign_digest_with_rng Result<()>>( &self, rng: &mut R, - msg_digest: D, + f: F, ) -> Result<(Signature, RecoveryId)> { - self.sign_prehash_with_rng(rng, &msg_digest.finalize_fixed()) + let mut digest = D::new(); + f(&mut digest)?; + self.sign_prehash_with_rng(rng, &digest.finalize_fixed()) } } @@ -304,7 +311,8 @@ where { fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<(Signature, RecoveryId)> { let mut digest = C::Digest::new(); - msg.iter().for_each(|slice| digest.update(slice)); + msg.iter() + .for_each(|slice| Digest::update(&mut digest, slice)); self.sign_digest_recoverable(digest) } } diff --git a/ecdsa/src/signing.rs b/ecdsa/src/signing.rs index 417ba7b7..060f27bc 100644 --- a/ecdsa/src/signing.rs +++ b/ecdsa/src/signing.rs @@ -145,8 +145,10 @@ where Scalar: Invert>>, SignatureSize: ArraySize, { - fn try_sign_digest(&self, msg_digest: D) -> Result> { - self.sign_prehash(&msg_digest.finalize_fixed()) + fn try_sign_digest Result<()>>(&self, f: F) -> Result> { + let mut digest = D::new(); + f(&mut digest)?; + self.sign_prehash(&digest.finalize_fixed()) } } @@ -188,9 +190,10 @@ where SignatureSize: ArraySize, { fn try_multipart_sign(&self, msg: &[&[u8]]) -> core::result::Result, Error> { - let mut digest = C::Digest::new(); - msg.iter().for_each(|slice| digest.update(slice)); - self.try_sign_digest(digest) + self.try_sign_digest(|digest: &mut C::Digest| { + msg.iter().for_each(|slice| digest.update(slice)); + Ok(()) + }) } } @@ -201,12 +204,14 @@ where Scalar: Invert>>, SignatureSize: ArraySize, { - fn try_sign_digest_with_rng( + fn try_sign_digest_with_rng Result<()>>( &self, rng: &mut R, - msg_digest: D, + f: F, ) -> Result> { - self.sign_prehash_with_rng(rng, &msg_digest.finalize_fixed()) + let mut digest = D::new(); + f(&mut digest)?; + self.sign_prehash_with_rng(rng, &digest.finalize_fixed()) } } @@ -264,9 +269,10 @@ where rng: &mut R, msg: &[&[u8]], ) -> Result> { - let mut digest = C::Digest::new(); - msg.iter().for_each(|slice| digest.update(slice)); - self.try_sign_digest_with_rng(rng, digest) + self.try_sign_digest_with_rng(rng, |digest: &mut C::Digest| { + msg.iter().for_each(|slice| digest.update(slice)); + Ok(()) + }) } } @@ -277,8 +283,8 @@ where Scalar: Invert>>, SignatureSize: ArraySize, { - fn try_sign_digest(&self, msg_digest: D) -> Result> { - let signature: Signature = self.try_sign_digest(msg_digest)?; + fn try_sign_digest Result<()>>(&self, f: F) -> Result> { + let signature: Signature = self.try_sign_digest(f)?; let oid = ecdsa_oid_for_digest(D::OID).ok_or_else(Error::new)?; SignatureWithOid::new(signature, oid) } @@ -304,9 +310,10 @@ where SignatureSize: ArraySize, { fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result> { - let mut digest = C::Digest::new(); - msg.iter().for_each(|slice| digest.update(slice)); - self.try_sign_digest(digest) + self.try_sign_digest(|digest: &mut C::Digest| { + msg.iter().for_each(|slice| digest.update(slice)); + Ok(()) + }) } } @@ -348,12 +355,12 @@ where der::MaxSize: ArraySize, as Add>::Output: Add + ArraySize, { - fn try_sign_digest_with_rng( + fn try_sign_digest_with_rng Result<()>>( &self, rng: &mut R, - msg_digest: D, + f: F, ) -> Result> { - RandomizedDigestSigner::>::try_sign_digest_with_rng(self, rng, msg_digest) + RandomizedDigestSigner::>::try_sign_digest_with_rng(self, rng, f) .map(Into::into) } } @@ -387,8 +394,8 @@ where der::MaxSize: ArraySize, as Add>::Output: Add + ArraySize, { - fn try_sign_digest(&self, msg_digest: D) -> Result> { - DigestSigner::>::try_sign_digest(self, msg_digest).map(Into::into) + fn try_sign_digest Result<()>>(&self, f: F) -> Result> { + DigestSigner::>::try_sign_digest(self, f).map(Into::into) } } diff --git a/ecdsa/src/verifying.rs b/ecdsa/src/verifying.rs index ada4ca31..b41bf41f 100644 --- a/ecdsa/src/verifying.rs +++ b/ecdsa/src/verifying.rs @@ -150,8 +150,14 @@ where D: Digest + FixedOutput, SignatureSize: ArraySize, { - fn verify_digest(&self, msg_digest: D, signature: &Signature) -> Result<()> { - self.verify_prehash(&msg_digest.finalize(), signature) + fn verify_digest Result<()>>( + &self, + f: F, + signature: &Signature, + ) -> Result<()> { + let mut digest = D::new(); + f(&mut digest)?; + self.verify_prehash(&digest.finalize(), signature) } } @@ -189,9 +195,13 @@ where SignatureSize: ArraySize, { fn multipart_verify(&self, msg: &[&[u8]], signature: &Signature) -> Result<()> { - let mut digest = C::Digest::new(); - msg.iter().for_each(|slice| digest.update(slice)); - self.verify_digest(digest, signature) + self.verify_digest( + |digest: &mut C::Digest| { + msg.iter().for_each(|slice| digest.update(slice)); + Ok(()) + }, + signature, + ) } } @@ -248,9 +258,13 @@ where der::MaxSize: ArraySize, as Add>::Output: Add + ArraySize, { - fn verify_digest(&self, msg_digest: D, signature: &der::Signature) -> Result<()> { + fn verify_digest Result<()>>( + &self, + f: F, + signature: &der::Signature, + ) -> Result<()> { let signature = Signature::::try_from(signature.clone())?; - DigestVerifier::>::verify_digest(self, msg_digest, &signature) + DigestVerifier::>::verify_digest(self, f, &signature) } } diff --git a/ml-dsa/Cargo.toml b/ml-dsa/Cargo.toml index b20d71e1..ca598243 100644 --- a/ml-dsa/Cargo.toml +++ b/ml-dsa/Cargo.toml @@ -36,7 +36,7 @@ hybrid-array = { version = "0.4", features = ["extra-sizes"] } num-traits = { version = "0.2.19", default-features = false } rand_core = { version = "0.9", optional = true } sha3 = "0.11.0-rc.0" -signature = { version = "3.0.0-rc.2", default-features = false } +signature = { version = "3.0.0-rc.2", default-features = false, features = ["digest"] } zeroize = { version = "1.8.1", optional = true, default-features = false } const-oid = { version = "0.10", features = ["db"], optional = true } diff --git a/ml-dsa/src/crypto.rs b/ml-dsa/src/crypto.rs index 50e273e8..8e938e6e 100644 --- a/ml-dsa/src/crypto.rs +++ b/ml-dsa/src/crypto.rs @@ -18,6 +18,10 @@ impl Default for ShakeState { } impl ShakeState { + pub fn pre_digest(digest: Shake) -> Self { + Self::Absorbing(digest) + } + pub fn absorb(mut self, input: &[u8]) -> Self { match &mut self { Self::Absorbing(sponge) => sponge.update(input), diff --git a/ml-dsa/src/lib.rs b/ml-dsa/src/lib.rs index fb381af7..8a4c529c 100644 --- a/ml-dsa/src/lib.rs +++ b/ml-dsa/src/lib.rs @@ -51,10 +51,16 @@ use hybrid_array::{ U75, U80, U88, Unsigned, }, }; +use signature::digest::Update; +use signature::{DigestSigner, DigestVerifier, MultipartSigner, MultipartVerifier, Signer}; + +#[cfg(feature = "rand_core")] +use signature::RandomizedDigestSigner; #[cfg(feature = "rand_core")] use rand_core::{CryptoRng, TryCryptoRng}; +use sha3::Shake256; #[cfg(feature = "zeroize")] use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -89,7 +95,7 @@ use core::fmt; pub use crate::param::{EncodedSignature, EncodedSigningKey, EncodedVerifyingKey, MlDsaParams}; pub use crate::util::B32; -pub use signature::{self, Error, MultipartSigner, MultipartVerifier}; +pub use signature::{self, Error}; /// An ML-DSA signature #[derive(Clone, PartialEq, Debug)] @@ -243,7 +249,7 @@ where /// The `Signer` implementation for `KeyPair` uses the optional deterministic variant of ML-DSA, and /// only supports signing with an empty context string. -impl signature::Signer> for KeyPair

{ +impl Signer> for KeyPair

{ fn try_sign(&self, msg: &[u8]) -> Result, Error> { self.try_multipart_sign(&[msg]) } @@ -257,6 +263,17 @@ impl MultipartSigner> for KeyPair

{ } } +/// The `DigestSigner` implementation for `KeyPair` uses the optional deterministic variant of ML-DSA +/// with a pre-computed μ, and only supports signing with an empty context string. +impl DigestSigner> for KeyPair

{ + fn try_sign_digest Result<(), Error>>( + &self, + f: F, + ) -> Result, Error> { + self.signing_key.try_sign_digest(&f) + } +} + #[cfg(feature = "pkcs8")] impl

SignatureAlgorithmIdentifier for KeyPair

where @@ -568,14 +585,14 @@ impl SigningKey

{ /// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and /// only supports signing with an empty context string. If you would like to include a context /// string, use the [`SigningKey::sign_deterministic`] method. -impl signature::Signer> for SigningKey

{ +impl Signer> for SigningKey

{ fn try_sign(&self, msg: &[u8]) -> Result, Error> { self.try_multipart_sign(&[msg]) } } /// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and -/// only supports signing with an empty context string. If you would like to include a context +/// only supports signing with an empty context string. If you would like to include a context /// string, use the [`SigningKey::sign_deterministic`] method. impl MultipartSigner> for SigningKey

{ fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result, Error> { @@ -583,6 +600,22 @@ impl MultipartSigner> for SigningKey

{ } } +/// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA +/// with a pre-computed µ, and only supports signing with an empty context string. If you would +/// like to include a context string, use the [`SigningKey::sign_mu_deterministic`] method. +impl DigestSigner> for SigningKey

{ + fn try_sign_digest Result<(), Error>>( + &self, + f: F, + ) -> Result, Error> { + let mut digest = Shake256::default().chain(self.tr).chain([0, 0]); + f(&mut digest)?; + let mu = H::pre_digest(digest).squeeze_new(); + + Ok(self.sign_mu_deterministic(&mu)) + } +} + /// The `KeyPair` implementation for `SigningKey` allows to derive a `VerifyingKey` from /// a bare `SigningKey` (even in the absence of the original seed). impl signature::Keypair for SigningKey

{ @@ -604,8 +637,8 @@ impl signature::Keypair for SigningKey

{ } /// The `RandomizedSigner` implementation for `SigningKey` only supports signing with an empty -/// context string. If you would like to include a context string, use the [`SigningKey::sign`] -/// method. +/// context string. If you would like to include a context string, use the +/// [`SigningKey::sign_randomized`] method. #[cfg(feature = "rand_core")] impl signature::RandomizedSigner> for SigningKey

{ fn try_sign_with_rng( @@ -617,6 +650,27 @@ impl signature::RandomizedSigner> for SigningKey

} } +/// The `RandomizedSigner` implementation for `SigningKey` only supports signing with an empty +/// context string. If you would like to include a context string, use the +/// [`SigningKey::sign_mu_randomized`] method. +#[cfg(feature = "rand_core")] +impl RandomizedDigestSigner> for SigningKey

{ + fn try_sign_digest_with_rng< + R: TryCryptoRng + ?Sized, + F: Fn(&mut Shake256) -> Result<(), Error>, + >( + &self, + rng: &mut R, + f: F, + ) -> Result, Error> { + let mut digest = Shake256::default().chain(self.tr).chain([0, 0]); + f(&mut digest)?; + let mu = H::pre_digest(digest).squeeze_new(); + + self.sign_mu_randomized(&mu, rng) + } +} + #[cfg(feature = "pkcs8")] impl

SignatureAlgorithmIdentifier for SigningKey

where @@ -777,6 +831,22 @@ impl MultipartVerifier> for VerifyingKey

{ } } +impl DigestVerifier> for VerifyingKey

{ + fn verify_digest Result<(), Error>>( + &self, + f: F, + signature: &Signature

, + ) -> Result<(), Error> { + let mut digest = Shake256::default().chain(self.tr).chain([0, 0]); + f(&mut digest)?; + let mu = H::pre_digest(digest).squeeze_new(); + + self.raw_verify_mu(&mu, signature) + .then_some(()) + .ok_or(Error::new()) + } +} + #[cfg(feature = "pkcs8")] impl

SignatureAlgorithmIdentifier for VerifyingKey

where @@ -1186,6 +1256,62 @@ mod test { sign_internal_verify_mu::(); } + #[test] + fn sign_digest_round_trip() { + fn sign_digest

() + where + P: MlDsaParams, + { + let kp = P::from_seed(&Array::default()); + let sk = kp.signing_key; + let vk = kp.verifying_key; + + let M = b"Hello world"; + let sig = sk.sign_digest(|digest| digest.update(M)); + assert_eq!(sig, sk.sign(M)); + + vk.verify_digest( + |digest| { + digest.update(M); + Ok(()) + }, + &sig, + ) + .unwrap(); + } + sign_digest::(); + sign_digest::(); + sign_digest::(); + } + + #[test] + #[cfg(feature = "rand_core")] + fn sign_randomized_digest_round_trip() { + fn sign_digest

() + where + P: MlDsaParams, + { + let kp = P::from_seed(&Array::default()); + let sk = kp.signing_key; + let vk = kp.verifying_key; + + let M = b"Hello world"; + let sig = sk.sign_digest_with_rng(&mut rand::rng(), |digest| digest.update(M)); + + vk.verify_digest( + |digest| { + digest.update(M); + Ok(()) + }, + &sig, + ) + .unwrap(); + } + sign_digest::(); + sign_digest::(); + sign_digest::(); + } + #[test] fn from_seed_implementations_match() { fn assert_from_seed_equality

()