Skip to main content

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(any(feature = "binary", feature = "text"))]
23    pub fn create_payload_hash(
24        &self,
25        payload_prefix: &[u8],
26        payload: &[u8],
27    ) -> Result<defuse_crypto::CryptoHash, StringError> {
28        use defuse_digest::{Digest, sha2::Sha256};
29
30        let domain_len = u32::try_from(self.domain.len())
31            .map_err(|_| tlb_ton::Error::custom("domain: overflow"))?;
32        let payload_len = u32::try_from(payload.len())
33            .map_err(|_| tlb_ton::Error::custom("payload: overflow"))?;
34
35        Ok(Sha256::new_with_prefix(b"\xFF\xFFton-connect/sign-data/")
36            .chain_update(self.address.workchain_id.to_be_bytes())
37            .chain_update(self.address.address)
38            .chain_update(domain_len.to_be_bytes())
39            .chain_update(self.domain.as_bytes())
40            .chain_update(self.timestamp.to_be_bytes())
41            .chain_update(payload_prefix)
42            .chain_update(payload_len.to_be_bytes())
43            .chain_update(payload)
44            .finalize()
45            .into())
46    }
47}
48
49pub trait PayloadSchema {
50    fn hash_with_context(
51        &self,
52        context: TonConnectPayloadContext,
53    ) -> Result<defuse_crypto::CryptoHash, StringError>;
54}
55
56/// See <https://docs.tonconsole.com/academy/sign-data#choosing-the-right-format>
57#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
58#[cfg_attr(
59    feature = "serde",
60    derive(::serde::Serialize, ::serde::Deserialize),
61    cfg_attr(feature = "abi", derive(::schemars::JsonSchema)),
62    serde(tag = "type", rename_all = "snake_case")
63)]
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum TonConnectPayloadSchema {
66    #[cfg(feature = "text")]
67    Text(text::TextPayload),
68    #[cfg(feature = "binary")]
69    Binary(binary::BinaryPayload),
70    #[cfg(feature = "cell")]
71    Cell(cell::CellPayload),
72}
73
74impl TonConnectPayloadSchema {
75    #[cfg(feature = "text")]
76    pub fn text(txt: impl Into<String>) -> Self {
77        Self::Text(text::TextPayload { text: txt.into() })
78    }
79
80    #[cfg(feature = "binary")]
81    pub fn binary(bytes: impl Into<Vec<u8>>) -> Self {
82        Self::Binary(binary::BinaryPayload {
83            bytes: bytes.into(),
84        })
85    }
86
87    #[cfg(feature = "cell")]
88    pub const fn cell(schema_crc: u32, cell: tlb_ton::Cell) -> Self {
89        Self::Cell(cell::CellPayload { schema_crc, cell })
90    }
91}
92
93impl PayloadSchema for TonConnectPayloadSchema {
94    fn hash_with_context(
95        &self,
96        context: TonConnectPayloadContext,
97    ) -> Result<defuse_crypto::CryptoHash, StringError> {
98        match self {
99            #[cfg(feature = "text")]
100            Self::Text(payload) => payload.hash_with_context(context),
101            #[cfg(feature = "binary")]
102            Self::Binary(payload) => payload.hash_with_context(context),
103            #[cfg(feature = "cell")]
104            Self::Cell(payload) => payload.hash_with_context(context),
105        }
106    }
107}