sui_graphql_rpc/types/
digest.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::string_input::impl_string_input;
5use async_graphql::*;
6use fastcrypto::encoding::{Base58, Encoding};
7use std::{fmt, str::FromStr};
8use sui_types::digests::{ObjectDigest, TransactionDigest};
9
10pub(crate) const BASE58_DIGEST_LENGTH: usize = 32;
11
12#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
13pub(crate) struct Digest([u8; BASE58_DIGEST_LENGTH]);
14
15#[derive(thiserror::Error, Debug)]
16pub(crate) enum Error {
17    #[error("Invalid Base58: {0}")]
18    InvalidBase58(String),
19
20    #[error("Expected digest to be {expect}B, but got {actual}B")]
21    BadDigestLength { expect: usize, actual: usize },
22}
23
24impl Digest {
25    pub(crate) fn to_vec(self) -> Vec<u8> {
26        self.0.to_vec()
27    }
28
29    pub(crate) fn as_slice(&self) -> &[u8] {
30        &self.0
31    }
32}
33
34impl_string_input!(Digest);
35
36impl FromStr for Digest {
37    type Err = Error;
38
39    fn from_str(s: &str) -> Result<Self, Self::Err> {
40        let buffer = Base58::decode(s).map_err(|_| Error::InvalidBase58(s.to_string()))?;
41        Digest::try_from(&buffer[..])
42    }
43}
44
45impl TryFrom<&[u8]> for Digest {
46    type Error = Error;
47
48    fn try_from(value: &[u8]) -> Result<Self, Error> {
49        let mut result = [0u8; BASE58_DIGEST_LENGTH];
50
51        if value.len() != BASE58_DIGEST_LENGTH {
52            return Err(Error::BadDigestLength {
53                expect: BASE58_DIGEST_LENGTH,
54                actual: value.len(),
55            });
56        }
57
58        result.copy_from_slice(value);
59        Ok(Digest(result))
60    }
61}
62
63impl From<Digest> for ObjectDigest {
64    fn from(digest: Digest) -> Self {
65        ObjectDigest::new(digest.0)
66    }
67}
68
69impl From<TransactionDigest> for Digest {
70    fn from(digest: TransactionDigest) -> Self {
71        Digest(digest.into_inner())
72    }
73}
74
75impl fmt::Display for Digest {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(f, "{}", Base58::encode(self.0))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::Error;
84    use super::*;
85
86    #[test]
87    fn test_base58_digest() {
88        let digest = [
89            183u8, 119, 223, 39, 204, 68, 220, 4, 126, 234, 232, 146, 106, 249, 98, 12, 170, 209,
90            98, 203, 243, 77, 154, 225, 177, 216, 169, 101, 51, 116, 79, 223,
91        ];
92
93        assert_eq!(
94            Digest::from_str("DMBdBZnpYR4EeTXzXL8A6BtVafqGjAWGsFZhP2zJYmXU").unwrap(),
95            Digest(digest)
96        );
97
98        assert!(matches!(
99            Digest::from_str("ILoveBase58").unwrap_err(),
100            Error::InvalidBase58(_),
101        ));
102
103        let long_digest = {
104            let mut bytes = vec![];
105            bytes.extend(digest);
106            bytes.extend(digest);
107            Base58::encode(bytes)
108        };
109
110        assert!(matches!(
111            Digest::from_str(&long_digest).unwrap_err(),
112            Error::BadDigestLength { .. },
113        ))
114    }
115}