Skip to main content

defuse_core/payload/
multi.rs

1use derive_more::derive::From;
2use serde::{Deserialize, Serialize, de::DeserializeOwned};
3use serde_with::serde_as;
4
5use crate::{
6    payload::{
7        Payload, SignedPayload, erc191::SignedErc191Payload, nep413::SignedNep413Payload,
8        sep53::SignedSep53Payload, tip191::SignedTip191Payload,
9        ton_connect::SignedTonConnectPayload,
10    },
11    public_key::PublicKey,
12};
13
14use super::{
15    DefusePayload, ExtractDefusePayload, raw::SignedRawEd25519Payload,
16    webauthn::SignedWebAuthnPayload,
17};
18
19#[serde_as]
20#[cfg_attr(feature = "schemars-v0_8", derive(::schemars::JsonSchema))]
21#[derive(Debug, Clone, Serialize, Deserialize, From)]
22#[serde(tag = "standard", rename_all = "snake_case")]
23/// Assuming wallets want to interact with Intents protocol, besides preparing the data in a certain
24/// form, they have to have the capability to sign raw messages (off-chain signatures) using an algorithm we understand.
25/// This enum solves that problem.
26///
27/// For example, because we support ERC-191 and know how to verify messages with that standard,
28/// we can allow wallets, like Metamask, sign messages to perform intents without having to
29/// support new cryptographic primitives and signing standards.
30pub enum MultiPayload {
31    /// NEP-413: The standard for message signing in Near Protocol.
32    /// For more details, refer to [NEP-413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md).
33    Nep413(SignedNep413Payload),
34
35    /// ERC-191: The standard for message signing in Ethereum, commonly used with `personal_sign()`.
36    /// For more details, refer to [EIP-191](https://eips.ethereum.org/EIPS/eip-191).
37    Erc191(SignedErc191Payload),
38
39    /// TIP-191: The standard for message signing in Tron.
40    /// For more details, refer to [TIP-191](https://github.com/tronprotocol/tips/blob/master/tip-191.md).
41    Tip191(SignedTip191Payload),
42
43    /// Raw Ed25519: The standard used by Solana Phantom wallets for message signing.
44    /// For more details, refer to [Phantom Wallet's documentation](https://docs.phantom.com/solana/signing-a-message).
45    RawEd25519(SignedRawEd25519Payload),
46
47    /// `WebAuthn`: The standard for Passkeys.
48    /// For more details, refer to [WebAuthn specification](https://w3c.github.io/webauthn/).
49    #[serde(rename = "webauthn")]
50    WebAuthn(SignedWebAuthnPayload),
51
52    /// `TonConnect`: The standard for data signing in TON blockchain platform.
53    /// For more details, refer to [TonConnect documentation](https://docs.tonconsole.com/academy/sign-data).
54    TonConnect(SignedTonConnectPayload),
55
56    /// SEP-53: The standard for signing data off-chain for Stellar accounts.
57    /// See [SEP-53](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md)
58    Sep53(SignedSep53Payload),
59}
60
61impl Payload for MultiPayload {
62    /// Hash of the envelope of the message.
63    /// Note that different arms will yield different hash values,
64    /// even if they include the same application-specific message in the envelope.
65    /// For example, NEP-413, uses SHA-256, while ERC-191 uses Keccak256.
66    #[inline]
67    fn hash(&self) -> [u8; 32] {
68        match self {
69            Self::Nep413(payload) => payload.hash(),
70            Self::Erc191(payload) => payload.hash(),
71            Self::Tip191(payload) => payload.hash(),
72            Self::RawEd25519(payload) => payload.hash(),
73            Self::WebAuthn(payload) => payload.hash(),
74            Self::TonConnect(payload) => payload.hash(),
75            Self::Sep53(payload) => payload.hash(),
76        }
77    }
78}
79
80impl SignedPayload for MultiPayload {
81    type PublicKey = PublicKey;
82
83    #[inline]
84    fn verify(&self) -> Option<Self::PublicKey> {
85        match self {
86            Self::Nep413(payload) => payload.verify().map(PublicKey::Ed25519),
87            Self::Erc191(payload) => payload.verify().map(PublicKey::Secp256k1),
88            Self::Tip191(payload) => payload.verify().map(PublicKey::Secp256k1),
89            Self::RawEd25519(payload) => payload.verify().map(PublicKey::Ed25519),
90            Self::WebAuthn(payload) => payload.verify(),
91            Self::TonConnect(payload) => payload.verify().map(PublicKey::Ed25519),
92            Self::Sep53(payload) => payload.verify().map(PublicKey::Ed25519),
93        }
94    }
95}
96
97impl<T> ExtractDefusePayload<T> for MultiPayload
98where
99    T: DeserializeOwned,
100{
101    type Error = serde_json::Error;
102
103    #[inline]
104    fn extract_defuse_payload(self) -> Result<DefusePayload<T>, Self::Error> {
105        match self {
106            Self::Nep413(payload) => payload.extract_defuse_payload(),
107            Self::Erc191(payload) => payload.extract_defuse_payload(),
108            Self::Tip191(payload) => payload.extract_defuse_payload(),
109            Self::RawEd25519(payload) => payload.extract_defuse_payload(),
110            Self::WebAuthn(payload) => payload.extract_defuse_payload(),
111            Self::TonConnect(payload) => payload.extract_defuse_payload(),
112            Self::Sep53(payload) => payload.extract_defuse_payload(),
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn raw_ed25519() {
123        let p: MultiPayload = serde_json::from_str(r#"{"standard":"raw_ed25519","payload":"{\"signer_id\":\"74affa71ab030d400fdfa1bed033dfa6fd3ae34f92d17c046ebe368e80d53751\",\"verifying_contract\":\"intents.near\",\"deadline\":{\"timestamp\":1732035219},\"nonce\":\"XVoKfmScb3G+XqH9ke/fSlJ/3xO59sNhCxhpG821BH8=\",\"intents\":[{\"intent\":\"token_diff\",\"diff\":{\"nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near\":\"-1000\",\"nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near\":\"998\"}}]}","public_key":"ed25519:8rVvtHWFr8hasdQGGD5WiQBTyr4iH2ruEPPVfj491RPN","signature":"ed25519:3vtbNQJHZfuV1s5DykzyjkbNLc583hnkrhTz57eDhd966iqzkor6Twgr4Loh2C195SCSEsiGfrd6KcxpjNq9ZbVj"}"#).unwrap();
124        assert_eq!(
125            bs58::encode(p.hash()).into_string(),
126            "8LKE47o44ybZQR9ozLyDnvMDTh4Ao5ipy2mJWsYByG5Q"
127        );
128        assert_eq!(
129            p.verify().unwrap(),
130            "ed25519:8rVvtHWFr8hasdQGGD5WiQBTyr4iH2ruEPPVfj491RPN"
131                .parse()
132                .unwrap()
133        );
134    }
135}