ring/ec/suite_b/ecdsa/
signing.rs

1// Copyright 2015-2016 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15//! ECDSA Signatures using the P-256 and P-384 curves.
16
17#![allow(clippy::cast_possible_truncation)] // XXX
18
19use super::digest_scalar::digest_scalar;
20use crate::{
21    arithmetic::montgomery::*,
22    cpu, digest,
23    ec::{
24        self,
25        suite_b::{ops::*, private_key},
26    },
27    error,
28    io::der,
29    limb, pkcs8, rand, sealed, signature,
30};
31/// An ECDSA signing algorithm.
32pub struct EcdsaSigningAlgorithm {
33    curve: &'static ec::Curve,
34    private_scalar_ops: &'static PrivateScalarOps,
35    private_key_ops: &'static PrivateKeyOps,
36    digest_alg: &'static digest::Algorithm,
37    pkcs8_template: &'static pkcs8::Template,
38    format_rs: fn(ops: &'static ScalarOps, r: &Scalar, s: &Scalar, out: &mut [u8]) -> usize,
39    id: AlgorithmID,
40}
41
42#[derive(Debug, Eq, PartialEq)]
43enum AlgorithmID {
44    ECDSA_P256_SHA256_FIXED_SIGNING,
45    ECDSA_P384_SHA384_FIXED_SIGNING,
46    ECDSA_P256_SHA256_ASN1_SIGNING,
47    ECDSA_P384_SHA384_ASN1_SIGNING,
48}
49
50derive_debug_via_id!(EcdsaSigningAlgorithm);
51
52impl PartialEq for EcdsaSigningAlgorithm {
53    fn eq(&self, other: &Self) -> bool {
54        self.id == other.id
55    }
56}
57
58impl Eq for EcdsaSigningAlgorithm {}
59
60impl sealed::Sealed for EcdsaSigningAlgorithm {}
61
62/// An ECDSA key pair, used for signing.
63pub struct EcdsaKeyPair {
64    d: Scalar<R>,
65    nonce_key: NonceRandomKey,
66    alg: &'static EcdsaSigningAlgorithm,
67    public_key: PublicKey,
68}
69
70derive_debug_via_field!(EcdsaKeyPair, stringify!(EcdsaKeyPair), public_key);
71
72impl EcdsaKeyPair {
73    /// Generates a new key pair and returns the key pair serialized as a
74    /// PKCS#8 document.
75    ///
76    /// The PKCS#8 document will be a v1 `OneAsymmetricKey` with the public key
77    /// included in the `ECPrivateKey` structure, as described in
78    /// [RFC 5958 Section 2] and [RFC 5915]. The `ECPrivateKey` structure will
79    /// not have a `parameters` field so the generated key is compatible with
80    /// PKCS#11.
81    ///
82    /// [RFC 5915]: https://tools.ietf.org/html/rfc5915
83    /// [RFC 5958 Section 2]: https://tools.ietf.org/html/rfc5958#section-2
84    pub fn generate_pkcs8(
85        alg: &'static EcdsaSigningAlgorithm,
86        rng: &dyn rand::SecureRandom,
87    ) -> Result<pkcs8::Document, error::Unspecified> {
88        let private_key = ec::Seed::generate(alg.curve, rng, cpu::features())?;
89        let public_key = private_key.compute_public_key()?;
90        Ok(pkcs8::wrap_key(
91            alg.pkcs8_template,
92            private_key.bytes_less_safe(),
93            public_key.as_ref(),
94        ))
95    }
96
97    /// Constructs an ECDSA key pair by parsing an unencrypted PKCS#8 v1
98    /// id-ecPublicKey `ECPrivateKey` key.
99    ///
100    /// The input must be in PKCS#8 v1 format. It must contain the public key in
101    /// the `ECPrivateKey` structure; `from_pkcs8()` will verify that the public
102    /// key and the private key are consistent with each other. The algorithm
103    /// identifier must identify the curve by name; it must not use an
104    /// "explicit" encoding of the curve. The `parameters` field of the
105    /// `ECPrivateKey`, if present, must be the same named curve that is in the
106    /// algorithm identifier in the PKCS#8 header.
107    pub fn from_pkcs8(
108        alg: &'static EcdsaSigningAlgorithm,
109        pkcs8: &[u8],
110        rng: &dyn rand::SecureRandom,
111    ) -> Result<Self, error::KeyRejected> {
112        let key_pair = ec::suite_b::key_pair_from_pkcs8(
113            alg.curve,
114            alg.pkcs8_template,
115            untrusted::Input::from(pkcs8),
116            cpu::features(),
117        )?;
118        Self::new(alg, key_pair, rng)
119    }
120
121    /// Constructs an ECDSA key pair from the private key and public key bytes
122    ///
123    /// The private key must encoded as a big-endian fixed-length integer. For
124    /// example, a P-256 private key must be 32 bytes prefixed with leading
125    /// zeros as needed.
126    ///
127    /// The public key is encoding in uncompressed form using the
128    /// Octet-String-to-Elliptic-Curve-Point algorithm in
129    /// [SEC 1: Elliptic Curve Cryptography, Version 2.0].
130    ///
131    /// This is intended for use by code that deserializes key pairs. It is
132    /// recommended to use `EcdsaKeyPair::from_pkcs8()` (with a PKCS#8-encoded
133    /// key) instead.
134    ///
135    /// [SEC 1: Elliptic Curve Cryptography, Version 2.0]:
136    ///     http://www.secg.org/sec1-v2.pdf
137    pub fn from_private_key_and_public_key(
138        alg: &'static EcdsaSigningAlgorithm,
139        private_key: &[u8],
140        public_key: &[u8],
141        rng: &dyn rand::SecureRandom,
142    ) -> Result<Self, error::KeyRejected> {
143        let key_pair = ec::suite_b::key_pair_from_bytes(
144            alg.curve,
145            untrusted::Input::from(private_key),
146            untrusted::Input::from(public_key),
147            cpu::features(),
148        )?;
149        Self::new(alg, key_pair, rng)
150    }
151
152    fn new(
153        alg: &'static EcdsaSigningAlgorithm,
154        key_pair: ec::KeyPair,
155        rng: &dyn rand::SecureRandom,
156    ) -> Result<Self, error::KeyRejected> {
157        let (seed, public_key) = key_pair.split();
158        let d = private_key::private_key_as_scalar(alg.private_key_ops, &seed);
159        let d = alg.private_scalar_ops.to_mont(&d);
160
161        let nonce_key = NonceRandomKey::new(alg, &seed, rng)?;
162        Ok(Self {
163            d,
164            nonce_key,
165            alg,
166            public_key: PublicKey(public_key),
167        })
168    }
169
170    /// Returns the signature of the `message` using a random nonce generated by `rng`.
171    pub fn sign(
172        &self,
173        rng: &dyn rand::SecureRandom,
174        message: &[u8],
175    ) -> Result<signature::Signature, error::Unspecified> {
176        // Step 4 (out of order).
177        let h = digest::digest(self.alg.digest_alg, message);
178
179        // Incorporate `h` into the nonce to hedge against faulty RNGs. (This
180        // is not an approved random number generator that is mandated in
181        // the spec.)
182        let nonce_rng = NonceRandom {
183            key: &self.nonce_key,
184            message_digest: &h,
185            rng,
186        };
187
188        self.sign_digest(h, &nonce_rng)
189    }
190
191    #[cfg(test)]
192    fn sign_with_fixed_nonce_during_test(
193        &self,
194        rng: &dyn rand::SecureRandom,
195        message: &[u8],
196    ) -> Result<signature::Signature, error::Unspecified> {
197        // Step 4 (out of order).
198        let h = digest::digest(self.alg.digest_alg, message);
199
200        self.sign_digest(h, rng)
201    }
202
203    /// Returns the signature of message digest `h` using a "random" nonce
204    /// generated by `rng`.
205    fn sign_digest(
206        &self,
207        h: digest::Digest,
208        rng: &dyn rand::SecureRandom,
209    ) -> Result<signature::Signature, error::Unspecified> {
210        // NSA Suite B Implementer's Guide to ECDSA Section 3.4.1: ECDSA
211        // Signature Generation.
212
213        // NSA Guide Prerequisites:
214        //
215        //     Prior to generating an ECDSA signature, the signatory shall
216        //     obtain:
217        //
218        //     1. an authentic copy of the domain parameters,
219        //     2. a digital signature key pair (d,Q), either generated by a
220        //        method from Appendix A.1, or obtained from a trusted third
221        //        party,
222        //     3. assurance of the validity of the public key Q (see Appendix
223        //        A.3), and
224        //     4. assurance that he/she/it actually possesses the associated
225        //        private key d (see [SP800-89] Section 6).
226        //
227        // The domain parameters are hard-coded into the source code.
228        // `EcdsaKeyPair::generate_pkcs8()` can be used to meet the second
229        // requirement; otherwise, it is up to the user to ensure the key pair
230        // was obtained from a trusted private key. The constructors for
231        // `EcdsaKeyPair` ensure that #3 and #4 are met subject to the caveats
232        // in SP800-89 Section 6.
233
234        let ops = self.alg.private_scalar_ops;
235        let scalar_ops = ops.scalar_ops;
236        let cops = scalar_ops.common;
237        let private_key_ops = self.alg.private_key_ops;
238
239        for _ in 0..100 {
240            // XXX: iteration conut?
241            // Step 1.
242            let k = private_key::random_scalar(self.alg.private_key_ops, rng)?;
243            let k_inv = ops.scalar_inv_to_mont(&k);
244
245            // Step 2.
246            let r = private_key_ops.point_mul_base(&k);
247
248            // Step 3.
249            let r = {
250                let (x, _) = private_key::affine_from_jacobian(private_key_ops, &r)?;
251                let x = cops.elem_unencoded(&x);
252                elem_reduced_to_scalar(cops, &x)
253            };
254            if cops.is_zero(&r) {
255                continue;
256            }
257
258            // Step 4 is done by the caller.
259
260            // Step 5.
261            let e = digest_scalar(scalar_ops, h);
262
263            // Step 6.
264            let s = {
265                let dr = scalar_ops.scalar_product(&self.d, &r);
266                let e_plus_dr = scalar_sum(cops, &e, dr);
267                scalar_ops.scalar_product(&k_inv, &e_plus_dr)
268            };
269            if cops.is_zero(&s) {
270                continue;
271            }
272
273            // Step 7 with encoding.
274            return Ok(signature::Signature::new(|sig_bytes| {
275                (self.alg.format_rs)(scalar_ops, &r, &s, sig_bytes)
276            }));
277        }
278
279        Err(error::Unspecified)
280    }
281}
282
283/// Generates an ECDSA nonce in a way that attempts to protect against a faulty
284/// `SecureRandom`.
285struct NonceRandom<'a> {
286    key: &'a NonceRandomKey,
287    message_digest: &'a digest::Digest,
288    rng: &'a dyn rand::SecureRandom,
289}
290
291impl core::fmt::Debug for NonceRandom<'_> {
292    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
293        f.debug_struct("NonceRandom").finish()
294    }
295}
296
297impl rand::sealed::SecureRandom for NonceRandom<'_> {
298    fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
299        // Use the same digest algorithm that will be used to digest the
300        // message. The digest algorithm's output is exactly the right size;
301        // this is checked below.
302        //
303        // XXX(perf): The single iteration will require two digest block
304        // operations because the amount of data digested is larger than one
305        // block.
306        let digest_alg = self.key.0.algorithm();
307        let mut ctx = digest::Context::new(digest_alg);
308
309        // Digest the randomized digest of the private key.
310        let key = self.key.0.as_ref();
311        ctx.update(key);
312
313        // The random value is digested between the key and the message so that
314        // the key and the message are not directly digested in the same digest
315        // block.
316        assert!(key.len() <= digest_alg.block_len() / 2);
317        {
318            let mut rand = [0u8; digest::MAX_BLOCK_LEN];
319            let rand = &mut rand[..digest_alg.block_len() - key.len()];
320            assert!(rand.len() >= dest.len());
321            self.rng.fill(rand)?;
322            ctx.update(rand);
323        }
324
325        ctx.update(self.message_digest.as_ref());
326
327        let nonce = ctx.finish();
328
329        // `copy_from_slice()` panics if the lengths differ, so we don't have
330        // to separately assert that the lengths are the same.
331        dest.copy_from_slice(nonce.as_ref());
332
333        Ok(())
334    }
335}
336
337impl<'a> sealed::Sealed for NonceRandom<'a> {}
338
339struct NonceRandomKey(digest::Digest);
340
341impl NonceRandomKey {
342    fn new(
343        alg: &EcdsaSigningAlgorithm,
344        seed: &ec::Seed,
345        rng: &dyn rand::SecureRandom,
346    ) -> Result<Self, error::KeyRejected> {
347        let mut rand = [0; digest::MAX_OUTPUT_LEN];
348        let rand = &mut rand[0..alg.curve.elem_scalar_seed_len];
349
350        // XXX: `KeyRejected` isn't the right way to model  failure of the RNG,
351        // but to fix that we'd need to break the API by changing the result type.
352        // TODO: Fix the API in the next breaking release.
353        rng.fill(rand)
354            .map_err(|error::Unspecified| error::KeyRejected::rng_failed())?;
355
356        let mut ctx = digest::Context::new(alg.digest_alg);
357        ctx.update(rand);
358        ctx.update(seed.bytes_less_safe());
359        Ok(Self(ctx.finish()))
360    }
361}
362
363impl signature::KeyPair for EcdsaKeyPair {
364    type PublicKey = PublicKey;
365
366    fn public_key(&self) -> &Self::PublicKey {
367        &self.public_key
368    }
369}
370
371#[derive(Clone, Copy)]
372pub struct PublicKey(ec::PublicKey);
373
374derive_debug_self_as_ref_hex_bytes!(PublicKey);
375
376impl AsRef<[u8]> for PublicKey {
377    fn as_ref(&self) -> &[u8] {
378        self.0.as_ref()
379    }
380}
381
382fn format_rs_fixed(ops: &'static ScalarOps, r: &Scalar, s: &Scalar, out: &mut [u8]) -> usize {
383    let scalar_len = ops.scalar_bytes_len();
384
385    let (r_out, rest) = out.split_at_mut(scalar_len);
386    limb::big_endian_from_limbs(ops.leak_limbs(r), r_out);
387
388    let (s_out, _) = rest.split_at_mut(scalar_len);
389    limb::big_endian_from_limbs(ops.leak_limbs(s), s_out);
390
391    2 * scalar_len
392}
393
394fn format_rs_asn1(ops: &'static ScalarOps, r: &Scalar, s: &Scalar, out: &mut [u8]) -> usize {
395    // This assumes `a` is not zero since neither `r` or `s` is allowed to be
396    // zero.
397    fn format_integer_tlv(ops: &ScalarOps, a: &Scalar, out: &mut [u8]) -> usize {
398        let mut fixed = [0u8; ec::SCALAR_MAX_BYTES + 1];
399        let fixed = &mut fixed[..(ops.scalar_bytes_len() + 1)];
400        limb::big_endian_from_limbs(ops.leak_limbs(a), &mut fixed[1..]);
401
402        // Since `a_fixed_out` is an extra byte long, it is guaranteed to start
403        // with a zero.
404        debug_assert_eq!(fixed[0], 0);
405
406        // There must be at least one non-zero byte since `a` isn't zero.
407        let first_index = fixed.iter().position(|b| *b != 0).unwrap();
408
409        // If the first byte has its high bit set, it needs to be prefixed with 0x00.
410        let first_index = if fixed[first_index] & 0x80 != 0 {
411            first_index - 1
412        } else {
413            first_index
414        };
415        let value = &fixed[first_index..];
416
417        out[0] = der::Tag::Integer as u8;
418
419        // Lengths less than 128 are encoded in one byte.
420        assert!(value.len() < 128);
421        out[1] = value.len() as u8;
422
423        out[2..][..value.len()].copy_from_slice(value);
424
425        2 + value.len()
426    }
427
428    out[0] = der::Tag::Sequence as u8;
429    let r_tlv_len = format_integer_tlv(ops, r, &mut out[2..]);
430    let s_tlv_len = format_integer_tlv(ops, s, &mut out[2..][r_tlv_len..]);
431
432    // Lengths less than 128 are encoded in one byte.
433    let value_len = r_tlv_len + s_tlv_len;
434    assert!(value_len < 128);
435    out[1] = value_len as u8;
436
437    2 + value_len
438}
439
440/// Signing of fixed-length (PKCS#11 style) ECDSA signatures using the
441/// P-256 curve and SHA-256.
442///
443/// See "`ECDSA_*_FIXED` Details" in `ring::signature`'s module-level
444/// documentation for more details.
445pub static ECDSA_P256_SHA256_FIXED_SIGNING: EcdsaSigningAlgorithm = EcdsaSigningAlgorithm {
446    curve: &ec::suite_b::curve::P256,
447    private_scalar_ops: &p256::PRIVATE_SCALAR_OPS,
448    private_key_ops: &p256::PRIVATE_KEY_OPS,
449    digest_alg: &digest::SHA256,
450    pkcs8_template: &EC_PUBLIC_KEY_P256_PKCS8_V1_TEMPLATE,
451    format_rs: format_rs_fixed,
452    id: AlgorithmID::ECDSA_P256_SHA256_FIXED_SIGNING,
453};
454
455/// Signing of fixed-length (PKCS#11 style) ECDSA signatures using the
456/// P-384 curve and SHA-384.
457///
458/// See "`ECDSA_*_FIXED` Details" in `ring::signature`'s module-level
459/// documentation for more details.
460pub static ECDSA_P384_SHA384_FIXED_SIGNING: EcdsaSigningAlgorithm = EcdsaSigningAlgorithm {
461    curve: &ec::suite_b::curve::P384,
462    private_scalar_ops: &p384::PRIVATE_SCALAR_OPS,
463    private_key_ops: &p384::PRIVATE_KEY_OPS,
464    digest_alg: &digest::SHA384,
465    pkcs8_template: &EC_PUBLIC_KEY_P384_PKCS8_V1_TEMPLATE,
466    format_rs: format_rs_fixed,
467    id: AlgorithmID::ECDSA_P384_SHA384_FIXED_SIGNING,
468};
469
470/// Signing of ASN.1 DER-encoded ECDSA signatures using the P-256 curve and
471/// SHA-256.
472///
473/// See "`ECDSA_*_ASN1` Details" in `ring::signature`'s module-level
474/// documentation for more details.
475pub static ECDSA_P256_SHA256_ASN1_SIGNING: EcdsaSigningAlgorithm = EcdsaSigningAlgorithm {
476    curve: &ec::suite_b::curve::P256,
477    private_scalar_ops: &p256::PRIVATE_SCALAR_OPS,
478    private_key_ops: &p256::PRIVATE_KEY_OPS,
479    digest_alg: &digest::SHA256,
480    pkcs8_template: &EC_PUBLIC_KEY_P256_PKCS8_V1_TEMPLATE,
481    format_rs: format_rs_asn1,
482    id: AlgorithmID::ECDSA_P256_SHA256_ASN1_SIGNING,
483};
484
485/// Signing of ASN.1 DER-encoded ECDSA signatures using the P-384 curve and
486/// SHA-384.
487///
488/// See "`ECDSA_*_ASN1` Details" in `ring::signature`'s module-level
489/// documentation for more details.
490pub static ECDSA_P384_SHA384_ASN1_SIGNING: EcdsaSigningAlgorithm = EcdsaSigningAlgorithm {
491    curve: &ec::suite_b::curve::P384,
492    private_scalar_ops: &p384::PRIVATE_SCALAR_OPS,
493    private_key_ops: &p384::PRIVATE_KEY_OPS,
494    digest_alg: &digest::SHA384,
495    pkcs8_template: &EC_PUBLIC_KEY_P384_PKCS8_V1_TEMPLATE,
496    format_rs: format_rs_asn1,
497    id: AlgorithmID::ECDSA_P384_SHA384_ASN1_SIGNING,
498};
499
500static EC_PUBLIC_KEY_P256_PKCS8_V1_TEMPLATE: pkcs8::Template = pkcs8::Template {
501    bytes: include_bytes!("ecPublicKey_p256_pkcs8_v1_template.der"),
502    alg_id_range: core::ops::Range { start: 8, end: 27 },
503    curve_id_index: 9,
504    private_key_index: 0x24,
505};
506
507static EC_PUBLIC_KEY_P384_PKCS8_V1_TEMPLATE: pkcs8::Template = pkcs8::Template {
508    bytes: include_bytes!("ecPublicKey_p384_pkcs8_v1_template.der"),
509    alg_id_range: core::ops::Range { start: 8, end: 24 },
510    curve_id_index: 9,
511    private_key_index: 0x23,
512};
513
514#[cfg(test)]
515mod tests {
516    use crate::{rand, signature, test};
517
518    #[test]
519    fn signature_ecdsa_sign_fixed_test() {
520        let rng = rand::SystemRandom::new();
521
522        test::run(
523            test_file!("ecdsa_sign_fixed_tests.txt"),
524            |section, test_case| {
525                assert_eq!(section, "");
526
527                let curve_name = test_case.consume_string("Curve");
528                let digest_name = test_case.consume_string("Digest");
529                let msg = test_case.consume_bytes("Msg");
530                let d = test_case.consume_bytes("d");
531                let q = test_case.consume_bytes("Q");
532                let k = test_case.consume_bytes("k");
533
534                let expected_result = test_case.consume_bytes("Sig");
535
536                let alg = match (curve_name.as_str(), digest_name.as_str()) {
537                    ("P-256", "SHA256") => &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
538                    ("P-384", "SHA384") => &signature::ECDSA_P384_SHA384_FIXED_SIGNING,
539                    _ => {
540                        panic!("Unsupported curve+digest: {}+{}", curve_name, digest_name);
541                    }
542                };
543
544                let private_key =
545                    signature::EcdsaKeyPair::from_private_key_and_public_key(alg, &d, &q, &rng)
546                        .unwrap();
547                let rng = test::rand::FixedSliceRandom { bytes: &k };
548
549                let actual_result = private_key
550                    .sign_with_fixed_nonce_during_test(&rng, &msg)
551                    .unwrap();
552
553                assert_eq!(actual_result.as_ref(), &expected_result[..]);
554
555                Ok(())
556            },
557        );
558    }
559
560    #[test]
561    fn signature_ecdsa_sign_asn1_test() {
562        let rng = rand::SystemRandom::new();
563
564        test::run(
565            test_file!("ecdsa_sign_asn1_tests.txt"),
566            |section, test_case| {
567                assert_eq!(section, "");
568
569                let curve_name = test_case.consume_string("Curve");
570                let digest_name = test_case.consume_string("Digest");
571                let msg = test_case.consume_bytes("Msg");
572                let d = test_case.consume_bytes("d");
573                let q = test_case.consume_bytes("Q");
574                let k = test_case.consume_bytes("k");
575
576                let expected_result = test_case.consume_bytes("Sig");
577
578                let alg = match (curve_name.as_str(), digest_name.as_str()) {
579                    ("P-256", "SHA256") => &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
580                    ("P-384", "SHA384") => &signature::ECDSA_P384_SHA384_ASN1_SIGNING,
581                    _ => {
582                        panic!("Unsupported curve+digest: {}+{}", curve_name, digest_name);
583                    }
584                };
585
586                let private_key =
587                    signature::EcdsaKeyPair::from_private_key_and_public_key(alg, &d, &q, &rng)
588                        .unwrap();
589                let rng = test::rand::FixedSliceRandom { bytes: &k };
590
591                let actual_result = private_key
592                    .sign_with_fixed_nonce_during_test(&rng, &msg)
593                    .unwrap();
594
595                assert_eq!(actual_result.as_ref(), &expected_result[..]);
596
597                Ok(())
598            },
599        );
600    }
601}