defuse_ton_connect/schema/
mod.rs

1use core::str;
2use std::borrow::Cow;
3use std::fmt::Debug;
4
5use tlb_ton::{MsgAddress, StringError};
6
7#[cfg(feature = "binary")]
8mod binary;
9#[cfg(feature = "cell")]
10mod cell;
11#[cfg(feature = "text")]
12mod text;
13
14pub struct TonConnectPayloadContext<'a> {
15    pub address: MsgAddress,
16    pub domain: Cow<'a, str>,
17    pub timestamp: u64,
18}
19
20impl TonConnectPayloadContext<'_> {
21    // See https://docs.tonconsole.com/academy/sign-data#how-the-signature-is-built
22    #[cfg(all(
23        any(feature = "near-contract", feature = "sha2"),
24        any(feature = "binary", feature = "text")
25    ))]
26    pub fn create_payload_hash(
27        &self,
28        payload_prefix: &[u8],
29        payload: &[u8],
30    ) -> Result<defuse_crypto::CryptoHash, StringError> {
31        use defuse_digest::Digest;
32        let domain_len = u32::try_from(self.domain.len())
33            .map_err(|_| tlb_ton::Error::custom("domain: overflow"))?;
34        let payload_len = u32::try_from(payload.len())
35            .map_err(|_| tlb_ton::Error::custom("payload: overflow"))?;
36
37        let bytes = [
38            [0xff, 0xff].as_slice(),
39            b"ton-connect/sign-data/",
40            &self.address.workchain_id.to_be_bytes(),
41            self.address.address.as_ref(),
42            &domain_len.to_be_bytes(),
43            self.domain.as_bytes(),
44            &self.timestamp.to_be_bytes(),
45            payload_prefix,
46            &payload_len.to_be_bytes(),
47            payload,
48        ]
49        .concat();
50
51        Ok(defuse_digest::Sha256::digest(&bytes).into())
52    }
53}
54
55#[cfg(any(feature = "near-contract", feature = "sha2"))]
56pub trait PayloadSchema {
57    fn hash_with_context(
58        &self,
59        context: TonConnectPayloadContext,
60    ) -> Result<defuse_crypto::CryptoHash, StringError>;
61}
62
63/// See <https://docs.tonconsole.com/academy/sign-data#choosing-the-right-format>
64#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
65#[cfg_attr(
66    feature = "serde",
67    derive(::serde::Serialize, ::serde::Deserialize),
68    cfg_attr(feature = "abi", derive(::schemars::JsonSchema)),
69    serde(tag = "type", rename_all = "snake_case")
70)]
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum TonConnectPayloadSchema {
73    #[cfg(feature = "text")]
74    Text(text::TextPayload),
75    #[cfg(feature = "binary")]
76    Binary(binary::BinaryPayload),
77    #[cfg(feature = "cell")]
78    Cell(cell::CellPayload),
79}
80
81impl TonConnectPayloadSchema {
82    #[cfg(feature = "text")]
83    pub fn text(txt: impl Into<String>) -> Self {
84        Self::Text(text::TextPayload { text: txt.into() })
85    }
86
87    #[cfg(feature = "binary")]
88    pub fn binary(bytes: impl Into<Vec<u8>>) -> Self {
89        Self::Binary(binary::BinaryPayload {
90            bytes: bytes.into(),
91        })
92    }
93
94    #[cfg(feature = "cell")]
95    pub const fn cell(schema_crc: u32, cell: tlb_ton::Cell) -> Self {
96        Self::Cell(cell::CellPayload { schema_crc, cell })
97    }
98}
99
100#[cfg(any(feature = "near-contract", feature = "sha2"))]
101impl PayloadSchema for TonConnectPayloadSchema {
102    fn hash_with_context(
103        &self,
104        context: TonConnectPayloadContext,
105    ) -> Result<defuse_crypto::CryptoHash, StringError> {
106        match self {
107            #[cfg(feature = "text")]
108            Self::Text(payload) => payload.hash_with_context(context),
109            #[cfg(feature = "binary")]
110            Self::Binary(payload) => payload.hash_with_context(context),
111            #[cfg(feature = "cell")]
112            Self::Cell(payload) => payload.hash_with_context(context),
113        }
114    }
115}