Skip to main content

defuse_core/payload/
ton_connect.rs

1use defuse_crypto::ed25519::{Ed25519PublicKey, Ed25519Signature};
2use defuse_ton_connect::TonConnect;
3pub use defuse_ton_connect::{TonConnectPayload, TonConnectPayloadSchema};
4use serde::{
5    Deserialize, Serialize,
6    de::{DeserializeOwned, Error},
7};
8
9use crate::payload::{Payload, SignedPayload};
10
11use super::{DefusePayload, ExtractDefusePayload};
12
13#[cfg_attr(feature = "schemars-v0_8", derive(::schemars::JsonSchema))]
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct SignedTonConnectPayload {
16    #[serde(flatten)]
17    pub payload: TonConnectPayload,
18
19    pub public_key: Ed25519PublicKey,
20    pub signature: Ed25519Signature,
21}
22
23impl Payload for SignedTonConnectPayload {
24    #[inline]
25    fn hash(&self) -> [u8; 32] {
26        self.payload.try_prehash().expect("ton-connect hash")
27    }
28}
29
30impl SignedPayload for SignedTonConnectPayload {
31    type PublicKey = Ed25519PublicKey;
32
33    #[inline]
34    fn verify(&self) -> Option<Self::PublicKey> {
35        TonConnect::verify(
36            &self.public_key.try_into().ok()?,
37            &self.payload,
38            &self.signature.into(),
39        )
40        .then_some(&self.public_key)
41        .copied()
42    }
43}
44
45impl<T> ExtractDefusePayload<T> for SignedTonConnectPayload
46where
47    T: DeserializeOwned,
48{
49    type Error = serde_json::Error;
50
51    #[inline]
52    fn extract_defuse_payload(self) -> Result<DefusePayload<T>, Self::Error> {
53        self.payload.extract_defuse_payload()
54    }
55}
56
57impl<T> ExtractDefusePayload<T> for TonConnectPayload
58where
59    T: DeserializeOwned,
60{
61    type Error = serde_json::Error;
62
63    fn extract_defuse_payload(self) -> Result<DefusePayload<T>, Self::Error> {
64        let TonConnectPayloadSchema::Text { text } = self.payload else {
65            return Err(Error::custom("only text payload supported"));
66        };
67
68        let p: DefusePayload<T> = serde_json::from_str(&text)?;
69
70        // TON Connect [specification](https://docs.tonconsole.com/academy/sign-data#in-a-smart-contract-on-chain)
71        // requires to check that "timestamp is recent". We don't have fixed TTL
72        // for off-chain signatures but rather check if `deadline` is not expired.
73        //
74        // At first, we were asserting `(timestamp <= now())`, but that  was causing
75        // `simulate_intents()` to fail, since sometimes signed intent is simulated
76        // right after signing.
77        //
78        // So, we ended up to assert at least following:
79        if p.deadline < self.timestamp {
80            return Err(Error::custom("deadline < timestamp"));
81        }
82
83        Ok(p)
84    }
85}