ssb_validate/
message.rs

1//! Functions for validating messages in the form of `KVT` (`key`, `value`, `timestamp`).
2use rayon::prelude::*;
3use serde::{Deserialize, Serialize};
4use snafu::{ensure, OptionExt, ResultExt};
5use ssb_legacy_msg_data::{
6    json::{from_slice, to_vec},
7    value::Value,
8};
9use ssb_multiformats::multihash::Multihash;
10
11use crate::error::{
12    ActualHashDidNotMatchKey, AuthorsDidNotMatch, InvalidMessage,
13    InvalidMessageCouldNotSerializeValue, InvalidMessageNoValue, InvalidPreviousMessage, Result,
14};
15use crate::message_value::{message_value_common_checks, SsbMessageValue};
16use crate::utils;
17
18/// Data type representing a `key-value` message object, where the `key` is a hash of the `value`.
19#[derive(Serialize, Deserialize, Debug)]
20pub struct SsbMessage {
21    pub key: Multihash,
22    pub value: SsbMessageValue,
23}
24
25/// Validate an out-of-order message without checking the author.
26///
27/// It expects the messages to be the JSON encoded message of shape: `{key: "", value: {...}}`
28///
29/// This checks that:
30/// - the _actual_ hash matches the hash claimed in `key`
31/// - the message contains the correct fields
32/// - the message value fields are in the correct order
33/// - there are no unexpected top-level fields in the message
34/// - the hash signature is defined as `sha256`
35/// - the message `content` string is canonical base64
36///
37/// This does not check:
38/// - the signature (see ssb-verify-signatures which lets you to batch verification of signatures)
39/// - the previous message
40///   - no check of the sequence to ensure it increments by 1 compared to previous
41///   - no check that the _actual_ hash of the previous message matches the hash claimed in `previous`
42///   - no check that the author has not changed
43pub fn validate_multi_author_message_hash_chain<T: AsRef<[u8]>>(message_bytes: T) -> Result<()> {
44    let message_bytes = message_bytes.as_ref();
45
46    let message = from_slice::<SsbMessage>(message_bytes).context(InvalidMessage {
47        message: message_bytes.to_owned(),
48    })?;
49
50    let message_value = message.value;
51
52    message_value_common_checks(&message_value, None, message_bytes, None, false)?;
53
54    let verifiable_msg: Value = from_slice(message_bytes).context(InvalidMessage {
55        message: message_bytes.to_owned(),
56    })?;
57
58    // Get the value from the message as this is what was hashed
59    let verifiable_msg_value = match verifiable_msg {
60        Value::Object(ref o) => o.get("value").context(InvalidMessageNoValue)?,
61        _ => panic!(),
62    };
63
64    // Get the "value" from the message as bytes that we can hash.
65    let value_bytes =
66        to_vec(verifiable_msg_value, false).context(InvalidMessageCouldNotSerializeValue)?;
67
68    let message_actual_multihash = utils::multihash_from_bytes(&value_bytes);
69
70    // The hash of the "value" must match the claimed value stored in the "key"
71    ensure!(
72        message_actual_multihash == message.key,
73        ActualHashDidNotMatchKey {
74            message: message_bytes.to_owned(),
75            actual_hash: message_actual_multihash,
76            expected_hash: message.key,
77        }
78    );
79
80    Ok(())
81}
82
83/// Batch validate a collection of out-of-order messages by multiple authors. No previous message
84/// checks are performed, meaning that missing messages are allowed, the collection is not expected
85/// to be ordered by ascending sequence number and the author is not expected to match between
86/// current and previous message.
87///
88/// It expects the messages to be the JSON encoded message of shape: `{key: "", value: {...}}`
89pub fn par_validate_multi_author_message_hash_chain_of_feed<T: AsRef<[u8]>>(
90    messages: &[T],
91) -> Result<()>
92where
93    [T]: ParallelSlice<T>,
94    T: Sync,
95{
96    messages
97        .par_iter()
98        .enumerate()
99        .try_fold(
100            || (),
101            |_, (_idx, msg)| validate_multi_author_message_hash_chain(msg.as_ref()),
102        )
103        .try_reduce(|| (), |_, _| Ok(()))
104}
105
106/// Validate an out-of-order message.
107///
108/// It expects the messages to be the JSON encoded message of shape: `{key: "", value: {...}}`
109///
110/// This checks that:
111/// - the author has not changed
112/// - the _actual_ hash matches the hash claimed in `key`
113/// - the message contains the correct fields
114///
115/// This does not check:
116/// - the signature. See ssb-verify-signatures which lets you to batch verification of signatures.
117/// - the sequence increments by 1 compared to previous
118/// - the _actual_ hash of the previous message matches the hash claimed in `previous`
119///
120/// `previous_msg_bytes` will be `None` only when `message_bytes` is the first message by that author.
121pub fn validate_ooo_message_hash_chain<T: AsRef<[u8]>, U: AsRef<[u8]>>(
122    message_bytes: T,
123    previous_msg_bytes: Option<U>,
124) -> Result<()> {
125    let message_bytes = message_bytes.as_ref();
126
127    let (previous_value, _previous_key) = match previous_msg_bytes {
128        Some(message) => {
129            let previous =
130                from_slice::<SsbMessage>(message.as_ref()).context(InvalidPreviousMessage {
131                    message: message.as_ref().to_owned(),
132                })?;
133            (Some(previous.value), Some(previous.key))
134        }
135        None => (None, None),
136    };
137
138    let message = from_slice::<SsbMessage>(message_bytes).context(InvalidMessage {
139        message: message_bytes.to_owned(),
140    })?;
141
142    let message_value = message.value;
143
144    message_value_common_checks(&message_value, None, message_bytes, None, false)?;
145
146    if let Some(previous_value) = previous_value.as_ref() {
147        // The authors are not allowed to change in a feed.
148        ensure!(
149            message_value.author == previous_value.author,
150            AuthorsDidNotMatch {
151                previous_author: previous_value.author.clone(),
152                author: message_value.author
153            }
154        );
155    }
156
157    let verifiable_msg: Value = from_slice(message_bytes).context(InvalidMessage {
158        message: message_bytes.to_owned(),
159    })?;
160
161    // Get the value from the message as this is what was hashed
162    let verifiable_msg_value = match verifiable_msg {
163        Value::Object(ref o) => o.get("value").context(InvalidMessageNoValue)?,
164        _ => panic!(),
165    };
166
167    // Get the "value" from the message as bytes that we can hash.
168    let value_bytes =
169        to_vec(verifiable_msg_value, false).context(InvalidMessageCouldNotSerializeValue)?;
170
171    let message_actual_multihash = utils::multihash_from_bytes(&value_bytes);
172
173    // The hash of the "value" must match the claimed value stored in the "key"
174    ensure!(
175        message_actual_multihash == message.key,
176        ActualHashDidNotMatchKey {
177            message: message_bytes.to_owned(),
178            actual_hash: message_actual_multihash,
179            expected_hash: message.key,
180        }
181    );
182
183    Ok(())
184}
185
186/// Batch validate a collection of out-of-order messages by a single author. Checks of previous
187/// message hash and ascending sequence number are not performed, meaning that missing
188/// messages are allowed and the collection is not expected to be ordered by ascending sequence
189/// number.
190///
191/// It expects the messages to be the JSON encoded message of shape: `{key: "", value: {...}}`
192pub fn par_validate_ooo_message_hash_chain_of_feed<T: AsRef<[u8]>>(messages: &[T]) -> Result<()>
193where
194    [T]: ParallelSlice<T>,
195    T: Sync,
196{
197    messages
198        .par_iter()
199        .enumerate()
200        .try_fold(
201            || (),
202            |_, (idx, msg)| {
203                if idx == 0 {
204                    validate_ooo_message_hash_chain::<_, &[u8]>(msg.as_ref(), None)
205                } else {
206                    validate_ooo_message_hash_chain(msg.as_ref(), Some(messages[idx - 1].as_ref()))
207                }
208            },
209        )
210        .try_reduce(|| (), |_, _| Ok(()))
211}
212
213/// Batch validate a collection of messages, all by the same author, ordered by ascending sequence
214/// number, with no missing messages.
215///
216/// It expects the messages to be the JSON encoded message of shape: `{key: "", value: {...}}`
217///
218/// This will mainly be useful during replication. Collect all the latest messages from a feed you're
219/// replicating and batch validate all the messages at once.
220///
221/// # Example
222///```
223///use ssb_validate::message::par_validate_message_hash_chain_of_feed;
224///let valid_message_1 = r##"{
225///  "key": "%/v5mCnV/kmnVtnF3zXtD4tbzoEQo4kRq/0d/bgxP1WI=.sha256",
226///  "value": {
227///    "previous": null,
228///    "author": "@U5GvOKP/YUza9k53DSXxT0mk3PIrnyAmessvNfZl5E0=.ed25519",
229///    "sequence": 1,
230///    "timestamp": 1470186877575,
231///    "hash": "sha256",
232///    "content": {
233///      "type": "about",
234///      "about": "@U5GvOKP/YUza9k53DSXxT0mk3PIrnyAmessvNfZl5E0=.ed25519",
235///      "name": "Piet"
236///    },
237///    "signature": "QJKWui3oyK6r5dH13xHkEVFhfMZDTXfK2tW21nyfheFClSf69yYK77Itj1BGcOimZ16pj9u3tMArLUCGSscqCQ==.sig.ed25519"
238///  },
239///  "timestamp": 1571140551481
240///}"##;
241///let valid_message_2 = r##"{
242///  "key": "%kLWDux4wCG+OdQWAHnpBGzGlCehqMLfgLbzlKCvgesU=.sha256",
243///  "value": {
244///    "previous": "%/v5mCnV/kmnVtnF3zXtD4tbzoEQo4kRq/0d/bgxP1WI=.sha256",
245///    "author": "@U5GvOKP/YUza9k53DSXxT0mk3PIrnyAmessvNfZl5E0=.ed25519",
246///    "sequence": 2,
247///    "timestamp": 1470187292812,
248///    "hash": "sha256",
249///    "content": {
250///      "type": "about",
251///      "about": "@U5GvOKP/YUza9k53DSXxT0mk3PIrnyAmessvNfZl5E0=.ed25519",
252///      "image": {
253///        "link": "&MxwsfZoq7X6oqnEX/TWIlAqd6S+jsUA6T1hqZYdl7RM=.sha256",
254///        "size": 642763,
255///        "type": "image/png",
256///        "width": 512,
257///        "height": 512
258///      }
259///    },
260///    "signature": "j3C7Us3JDnSUseF4ycRB0dTMs0xC6NAriAFtJWvx2uyz0K4zSj6XL8YA4BVqv+AHgo08+HxXGrpJlZ3ADwNnDw==.sig.ed25519"
261///  },
262///  "timestamp": 1571140551485
263///}"##;
264/// let messages = [valid_message_1.as_bytes(), valid_message_2.as_bytes()];
265/// // If you're passing `None` as the `previous` argument you'll need to give the compiler a hint about
266/// // the type.
267/// let result = par_validate_message_hash_chain_of_feed::<_, &[u8]>(&messages, None);
268/// assert!(result.is_ok());
269///```
270pub fn par_validate_message_hash_chain_of_feed<T: AsRef<[u8]>, U: AsRef<[u8]>>(
271    messages: &[T],
272    previous: Option<U>,
273) -> Result<()>
274where
275    [T]: ParallelSlice<T>,
276    T: Sync,
277    U: Sync + Send + Copy,
278{
279    messages
280        .par_iter()
281        .enumerate()
282        .try_fold(
283            || (),
284            |_, (idx, msg)| {
285                if idx == 0 {
286                    let prev = previous.map(|prev| prev.as_ref().to_owned());
287                    validate_message_hash_chain(msg.as_ref(), prev)
288                } else {
289                    validate_message_hash_chain(msg.as_ref(), Some(messages[idx - 1].as_ref()))
290                }
291            },
292        )
293        .try_reduce(|| (), |_, _| Ok(()))
294}
295
296/// Validate a message in relation to the previous message.
297///
298/// It expects the messages to be the JSON encoded message of shape: `{key: "", value: {...}}`
299///
300/// This checks that:
301/// - the sequence starts at one if it's the first message
302/// - the previous is correctly set to null if it's the first message
303/// - the sequence increments correctly
304/// - the author has not changed
305/// - the feed is not forked
306/// - the _actual_ hash matches the hash claimed in `key`
307///
308/// This does not check:
309/// - the signature. See ssb-verify-signatures which lets you to batch verification of signatures.
310///
311/// `previous_msg_bytes` will be `None` only when `message_bytes` is the first message by that author.
312///
313/// # Example
314///```
315///use ssb_validate::message::validate_message_hash_chain;
316///let valid_message_1 = r##"{
317///  "key": "%/v5mCnV/kmnVtnF3zXtD4tbzoEQo4kRq/0d/bgxP1WI=.sha256",
318///  "value": {
319///    "previous": null,
320///    "author": "@U5GvOKP/YUza9k53DSXxT0mk3PIrnyAmessvNfZl5E0=.ed25519",
321///    "sequence": 1,
322///    "timestamp": 1470186877575,
323///    "hash": "sha256",
324///    "content": {
325///      "type": "about",
326///      "about": "@U5GvOKP/YUza9k53DSXxT0mk3PIrnyAmessvNfZl5E0=.ed25519",
327///      "name": "Piet"
328///    },
329///    "signature": "QJKWui3oyK6r5dH13xHkEVFhfMZDTXfK2tW21nyfheFClSf69yYK77Itj1BGcOimZ16pj9u3tMArLUCGSscqCQ==.sig.ed25519"
330///  },
331///  "timestamp": 1571140551481
332///}"##;
333///let valid_message_2 = r##"{
334///  "key": "%kLWDux4wCG+OdQWAHnpBGzGlCehqMLfgLbzlKCvgesU=.sha256",
335///  "value": {
336///    "previous": "%/v5mCnV/kmnVtnF3zXtD4tbzoEQo4kRq/0d/bgxP1WI=.sha256",
337///    "author": "@U5GvOKP/YUza9k53DSXxT0mk3PIrnyAmessvNfZl5E0=.ed25519",
338///    "sequence": 2,
339///    "timestamp": 1470187292812,
340///    "hash": "sha256",
341///    "content": {
342///      "type": "about",
343///      "about": "@U5GvOKP/YUza9k53DSXxT0mk3PIrnyAmessvNfZl5E0=.ed25519",
344///      "image": {
345///        "link": "&MxwsfZoq7X6oqnEX/TWIlAqd6S+jsUA6T1hqZYdl7RM=.sha256",
346///        "size": 642763,
347///        "type": "image/png",
348///        "width": 512,
349///        "height": 512
350///      }
351///    },
352///    "signature": "j3C7Us3JDnSUseF4ycRB0dTMs0xC6NAriAFtJWvx2uyz0K4zSj6XL8YA4BVqv+AHgo08+HxXGrpJlZ3ADwNnDw==.sig.ed25519"
353///  },
354///  "timestamp": 1571140551485
355///}"##;
356/// let result = validate_message_hash_chain(valid_message_2.as_bytes(), Some(valid_message_1));
357/// assert!(result.is_ok());
358///```
359pub fn validate_message_hash_chain<T: AsRef<[u8]>, U: AsRef<[u8]>>(
360    message_bytes: T,
361    previous_msg_bytes: Option<U>,
362) -> Result<()> {
363    let message_bytes = message_bytes.as_ref();
364    // msg seq is 1 larger than previous
365    let (previous_value, previous_key) = match previous_msg_bytes {
366        Some(message) => {
367            let previous =
368                from_slice::<SsbMessage>(message.as_ref()).context(InvalidPreviousMessage {
369                    message: message.as_ref().to_owned(),
370                })?;
371            (Some(previous.value), Some(previous.key))
372        }
373
374        None => (None, None),
375    };
376
377    let message = from_slice::<SsbMessage>(message_bytes).context(InvalidMessage {
378        message: message_bytes.to_owned(),
379    })?;
380
381    let message_value = message.value;
382
383    message_value_common_checks(
384        &message_value,
385        previous_value.as_ref(),
386        message_bytes,
387        previous_key.as_ref(),
388        // run checks for previous msg
389        true,
390    )?;
391
392    let verifiable_msg: Value = from_slice(message_bytes).context(InvalidMessage {
393        message: message_bytes.to_owned(),
394    })?;
395
396    // Get the value from the message as this is what was hashed
397    let verifiable_msg_value = match verifiable_msg {
398        Value::Object(ref o) => o.get("value").context(InvalidMessageNoValue)?,
399        _ => panic!(),
400    };
401
402    // Get the "value" from the message as bytes that we can hash.
403    let value_bytes =
404        to_vec(verifiable_msg_value, false).context(InvalidMessageCouldNotSerializeValue)?;
405
406    let message_actual_multihash = utils::multihash_from_bytes(&value_bytes);
407
408    // The hash of the "value" must match the claimed value stored in the "key"
409    ensure!(
410        message_actual_multihash == message.key,
411        ActualHashDidNotMatchKey {
412            message: message_bytes.to_owned(),
413            actual_hash: message_actual_multihash,
414            expected_hash: message.key,
415        }
416    );
417
418    Ok(())
419}
420
421#[cfg(test)]
422mod tests {
423    use crate::error::Error;
424    use crate::message::{
425        par_validate_message_hash_chain_of_feed,
426        par_validate_multi_author_message_hash_chain_of_feed,
427        par_validate_ooo_message_hash_chain_of_feed, validate_message_hash_chain,
428        validate_multi_author_message_hash_chain, validate_ooo_message_hash_chain,
429    };
430    use crate::test_data::*;
431
432    #[test]
433    fn it_works_multi_author() {
434        assert!(validate_multi_author_message_hash_chain(MESSAGE_2.as_bytes()).is_ok());
435    }
436
437    #[test]
438    fn it_works_ooo_messages_without_first_message() {
439        assert!(
440            validate_ooo_message_hash_chain(MESSAGE_2.as_bytes(), Some(MESSAGE_3.as_bytes()))
441                .is_ok()
442        );
443    }
444
445    #[test]
446    fn it_works_ooo_messages() {
447        assert!(
448            validate_ooo_message_hash_chain(MESSAGE_3.as_bytes(), Some(MESSAGE_1.as_bytes()))
449                .is_ok()
450        );
451    }
452
453    #[test]
454    fn it_validates_a_private_message_ooo() {
455        let result = validate_ooo_message_hash_chain::<_, &[u8]>(MESSAGE_PRIVATE.as_bytes(), None);
456
457        assert!(result.is_ok());
458    }
459
460    #[test]
461    fn it_detects_invalid_base64_for_private_message_ooo() {
462        let result =
463            validate_ooo_message_hash_chain::<_, &[u8]>(MESSAGE_PRIVATE_INVALID.as_bytes(), None);
464        match result {
465            Err(Error::InvalidBase64 { message: _ }) => {}
466            _ => panic!(),
467        }
468    }
469
470    #[test]
471    fn par_validate_multi_author_message_hash_chain_of_feed_works() {
472        let messages = [
473            MESSAGE_WITH_UNICODE.as_bytes(),
474            MESSAGE_PRIVATE.as_bytes(),
475            MESSAGE_1.as_bytes(),
476        ];
477
478        let result = par_validate_multi_author_message_hash_chain_of_feed(&messages[..]);
479        assert!(result.is_ok());
480    }
481
482    #[test]
483    fn par_validate_ooo_message_hash_chain_of_feed_with_first_message_works() {
484        let messages = [
485            MESSAGE_1.as_bytes(),
486            MESSAGE_3.as_bytes(),
487            MESSAGE_2.as_bytes(),
488        ];
489
490        let result = par_validate_ooo_message_hash_chain_of_feed(&messages[..]);
491        assert!(result.is_ok());
492    }
493
494    #[test]
495    fn par_validate_ooo_message_hash_chain_of_feed_without_first_message_works() {
496        let messages = [MESSAGE_3.as_bytes(), MESSAGE_2.as_bytes()];
497
498        let result = par_validate_ooo_message_hash_chain_of_feed(&messages[..]);
499        assert!(result.is_ok());
500    }
501
502    #[test]
503    fn it_works_first_message() {
504        assert!(validate_message_hash_chain::<_, &[u8]>(MESSAGE_1.as_bytes(), None).is_ok());
505    }
506
507    #[test]
508    fn it_works_second_message() {
509        assert!(
510            validate_message_hash_chain(MESSAGE_2.as_bytes(), Some(MESSAGE_1.as_bytes())).is_ok()
511        );
512    }
513
514    #[test]
515    fn par_validate_message_hash_chain_of_feed_first_messages_works() {
516        let messages = [MESSAGE_1.as_bytes(), MESSAGE_2.as_bytes()];
517
518        let result = par_validate_message_hash_chain_of_feed::<_, &[u8]>(&messages[..], None);
519        assert!(result.is_ok());
520    }
521
522    #[test]
523    fn par_validate_message_hash_chain_of_feed_with_prev_works() {
524        let messages = [MESSAGE_2.as_bytes(), MESSAGE_3.as_bytes()];
525
526        let result =
527            par_validate_message_hash_chain_of_feed(&messages[..], Some(MESSAGE_1.as_bytes()));
528        assert!(result.is_ok());
529    }
530
531    #[test]
532    fn first_message_must_have_previous_of_null() {
533        let result =
534            validate_message_hash_chain::<_, &[u8]>(MESSAGE_1_INVALID_PREVIOUS.as_bytes(), None);
535        match result {
536            Err(Error::FirstMessageDidNotHavePreviousOfNull { message: _ }) => {}
537            _ => panic!(),
538        }
539    }
540
541    #[test]
542    fn first_message_must_have_sequence_of_one() {
543        let result =
544            validate_message_hash_chain::<_, &[u8]>(MESSAGE_1_INVALID_SEQ.as_bytes(), None);
545        match result {
546            Err(Error::FirstMessageDidNotHaveSequenceOfOne { message: _ }) => {}
547            _ => panic!(),
548        }
549    }
550
551    #[test]
552    fn it_detects_incorrect_seq() {
553        let result = validate_message_hash_chain(
554            MESSAGE_2_INCORRECT_SEQUENCE.as_bytes(),
555            Some(MESSAGE_1.as_bytes()),
556        );
557        match result {
558            Err(Error::InvalidSequenceNumber {
559                message: _,
560                actual,
561                expected,
562            }) => {
563                assert_eq!(actual, 3);
564                assert_eq!(expected, 2);
565            }
566            _ => panic!(),
567        }
568    }
569
570    #[test]
571    fn it_detects_incorrect_author() {
572        let result = validate_message_hash_chain(
573            MESSAGE_2_INCORRECT_AUTHOR.as_bytes(),
574            Some(MESSAGE_1.as_bytes()),
575        );
576        match result {
577            Err(Error::AuthorsDidNotMatch {
578                previous_author: _,
579                author: _,
580            }) => {}
581            _ => panic!(),
582        }
583    }
584
585    #[test]
586    fn it_detects_incorrect_previous_of_null() {
587        let result = validate_message_hash_chain(
588            MESSAGE_2_PREVIOUS_NULL.as_bytes(),
589            Some(MESSAGE_1.as_bytes()),
590        );
591        match result {
592            Err(Error::PreviousWasNull) => {}
593            _ => panic!(),
594        }
595    }
596
597    #[test]
598    fn it_detects_incorrect_key() {
599        let result = validate_message_hash_chain(
600            MESSAGE_2_INCORRECT_KEY.as_bytes(),
601            Some(MESSAGE_1.as_bytes()),
602        );
603        match result {
604            Err(Error::ActualHashDidNotMatchKey {
605                message: _,
606                expected_hash: _,
607                actual_hash: _,
608            }) => {}
609            _ => panic!(),
610        }
611    }
612
613    #[test]
614    fn it_detects_incorrect_key_for_multi_author() {
615        let result = validate_multi_author_message_hash_chain(MESSAGE_2_INCORRECT_KEY.as_bytes());
616        match result {
617            Err(Error::ActualHashDidNotMatchKey {
618                message: _,
619                expected_hash: _,
620                actual_hash: _,
621            }) => {}
622            _ => panic!(),
623        }
624    }
625
626    #[test]
627    fn it_detects_extra_unwanted_field() {
628        let result =
629            validate_message_hash_chain::<_, &[u8]>(MESSAGE_WITH_EXTRA_FIELD.as_bytes(), None);
630        // code: Message("unknown field `extra`, expected one of ...
631        match result {
632            Err(Error::InvalidMessage {
633                source: _,
634                message: _,
635            }) => {}
636            _ => panic!(),
637        }
638    }
639
640    #[test]
641    fn it_detects_fork() {
642        let result =
643            validate_message_hash_chain(MESSAGE_2_FORK.as_bytes(), Some(MESSAGE_1.as_bytes()));
644        match result {
645            Err(Error::ForkedFeed { previous_seq: 1 }) => {}
646            _ => panic!(),
647        }
648    }
649
650    #[test]
651    fn it_detects_missing_hash_function() {
652        let result =
653            validate_message_hash_chain::<_, &[u8]>(MESSAGE_WITHOUT_HASH_FUNCTION.as_bytes(), None);
654        match result {
655            Err(Error::InvalidMessage {
656                source: _,
657                message: _,
658            }) => {}
659            _ => panic!(),
660        }
661    }
662
663    #[test]
664    fn it_detects_incorrect_hash_function() {
665        let result = validate_message_hash_chain::<_, &[u8]>(
666            MESSAGE_WITH_INVALID_HASH_FUNCTION.as_bytes(),
667            None,
668        );
669        match result {
670            Err(Error::InvalidHashFunction { message: _ }) => {}
671            _ => panic!(),
672        }
673    }
674
675    #[test]
676    fn it_validates_a_message_with_unicode() {
677        let result = validate_message_hash_chain(
678            MESSAGE_WITH_UNICODE.as_bytes(),
679            Some(MESSAGE_WITH_UNICODE_PREV.as_bytes()),
680        );
681
682        assert!(result.is_ok());
683    }
684
685    #[test]
686    fn it_detects_incorrect_message_value_order() {
687        let result = validate_message_hash_chain(
688            MESSAGE_2_INVALID_ORDER.as_bytes(),
689            Some(MESSAGE_1.as_bytes()),
690        );
691        match result {
692            Err(Error::InvalidMessageValueOrder { message: _ }) => {}
693            _ => panic!(),
694        }
695    }
696
697    #[test]
698    fn it_validates_a_private_message() {
699        let result = validate_message_hash_chain(
700            MESSAGE_PRIVATE.as_bytes(),
701            Some(MESSAGE_PRIVATE_PREV.as_bytes()),
702        );
703
704        assert!(result.is_ok());
705    }
706
707    #[test]
708    fn it_detects_invalid_base64_for_private_message() {
709        let result = validate_message_hash_chain(
710            MESSAGE_PRIVATE_INVALID.as_bytes(),
711            Some(MESSAGE_PRIVATE_PREV.as_bytes()),
712        );
713        match result {
714            Err(Error::InvalidBase64 { message: _ }) => {}
715            _ => panic!(),
716        }
717    }
718}