defuse_core/payload/
multi.rs

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