defuse_core/payload/
multi.rs

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