penumbra_dex/swap/
payload.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
use anyhow::anyhow;
use penumbra_keys::FullViewingKey;
use penumbra_proto::penumbra::core::component::dex::v1 as pb;
use serde::{Deserialize, Serialize};

use super::{SwapCiphertext, SwapPlaintext};

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(try_from = "pb::SwapPayload", into = "pb::SwapPayload")]
pub struct SwapPayload {
    pub commitment: penumbra_tct::StateCommitment,
    pub encrypted_swap: SwapCiphertext,
}

impl SwapPayload {
    pub fn trial_decrypt(&self, fvk: &FullViewingKey) -> Option<SwapPlaintext> {
        // Try to decrypt the swap ciphertext. If it doesn't decrypt, it wasn't meant for us.
        let swap = self
            .encrypted_swap
            .decrypt(fvk.outgoing(), self.commitment)
            .ok()?;
        tracing::debug!(swap_commitment = ?self.commitment, ?swap, "found swap while scanning");

        // Before returning, though, we want to perform integrity checks on the
        // swap plaintext, since it could have been sent by unseen overlords of
        // endless deceptive power. One can never be too careful.
        //
        // As in trial_decrypt for notes, we don't want to return errors, to
        // avoid the possibility of "REJECT" style attacks.

        // Check that the swap plaintext matches the swap commitment.
        if swap.swap_commitment() != self.commitment {
            // This should be a warning, because no honestly generated swap plaintext should
            // fail to match the swap commitment actually included in the chain.
            tracing::warn!("decrypted swap does not match provided swap commitment");
            return None;
        }

        // Check that the swap outputs are spendable by this fvk's spending key.
        if !fvk.incoming().views_address(&swap.claim_address) {
            // This should be a warning, because no honestly generated swap plaintext should
            // mismatch the FVK that can detect and decrypt it.
            tracing::warn!("decrypted swap that is not claimable by provided full viewing key");
            return None;
        }

        Some(swap)
    }
}

impl From<SwapPayload> for pb::SwapPayload {
    fn from(msg: SwapPayload) -> Self {
        pb::SwapPayload {
            commitment: Some(msg.commitment.into()),
            encrypted_swap: msg.encrypted_swap.0.to_vec(),
        }
    }
}

impl TryFrom<pb::SwapPayload> for SwapPayload {
    type Error = anyhow::Error;

    fn try_from(msg: pb::SwapPayload) -> Result<Self, Self::Error> {
        let commitment = msg
            .commitment
            .ok_or_else(|| anyhow!("missing commitment"))?
            .try_into()?;
        let encrypted_swap = SwapCiphertext(
            msg.encrypted_swap
                .try_into()
                .map_err(|_| anyhow!("expected correct length swap ciphertext"))?,
        );
        Ok(Self {
            commitment,
            encrypted_swap,
        })
    }
}