Skip to main content

defuse_core/payload/
multi.rs

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