Skip to main content

chia_sdk_driver/clear_signing/
vault_spend.rs

1use chia_consensus::opcodes::SEND_MESSAGE;
2use chia_protocol::Bytes32;
3use chia_sdk_types::Condition;
4use clvm_traits::FromClvm;
5use clvmr::{Allocator, NodePtr};
6use num_bigint::BigInt;
7
8use crate::{DriverError, Facts, Spend, VaultMessage, parse_delegated_spend, parse_vault_message};
9
10#[derive(Debug, Clone)]
11pub struct VaultSpendSummary {
12    pub child: Option<VaultOutput>,
13    pub drop_coins: Vec<DropCoin>,
14    pub messages: Vec<VaultMessage>,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct VaultOutput {
19    pub custody_hash: Bytes32,
20    pub amount: u64,
21}
22
23impl VaultOutput {
24    pub fn new(custody_hash: Bytes32, amount: u64) -> Self {
25        Self {
26            custody_hash,
27            amount,
28        }
29    }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub struct DropCoin {
34    pub puzzle_hash: Bytes32,
35    pub amount: u64,
36}
37
38impl DropCoin {
39    pub fn new(puzzle_hash: Bytes32, amount: u64) -> Self {
40        Self {
41            puzzle_hash,
42            amount,
43        }
44    }
45}
46
47pub fn parse_vault_delegated_spend(
48    facts: &mut Facts,
49    allocator: &mut Allocator,
50    delegated_spend: Spend,
51) -> Result<VaultSpendSummary, DriverError> {
52    let conditions = parse_delegated_spend(allocator, delegated_spend)?;
53
54    let mut child = None;
55    let mut drop_coins = Vec::new();
56    let mut messages = Vec::new();
57
58    for condition in conditions {
59        match condition {
60            Condition::AssertPuzzleAnnouncement(condition) => {
61                facts.assert_puzzle_announcement(condition.announcement_id);
62            }
63            Condition::AssertConcurrentSpend(condition) => {
64                facts.assert_spend(condition.coin_id);
65            }
66            Condition::AssertBeforeSecondsAbsolute(condition) => {
67                facts.update_actual_expiration_time(condition.seconds);
68            }
69            Condition::ReserveFee(condition) => {
70                facts.add_reserved_fees(condition.amount);
71            }
72            Condition::CreateCoin(condition) => {
73                // If a child of a singleton (the vault in this case) is odd, due to the way the singleton
74                // puzzle works, we know that it will be the new coin for this singleton. And because this
75                // parsing is running at the delegated spend layer (i.e., inner puzzle), we know that the
76                // puzzle hash is actually the custody hash, rather than the singleton's full puzzle hash.
77                //
78                // Note that if a CREATE_COIN condition with an odd child is not included in the delegated
79                // spend, we assume that the singleton is being melted. This is technically only true if
80                // the special (puzzle specific) MELT_SINGLETON condition is included, but if it's not, then
81                // the transaction is invalid anyways.
82                if condition.amount % 2 == 1 {
83                    child = Some(VaultOutput {
84                        custody_hash: condition.puzzle_hash,
85                        amount: condition.amount,
86                    });
87                } else {
88                    drop_coins.push(DropCoin {
89                        puzzle_hash: condition.puzzle_hash,
90                        amount: condition.amount,
91                    });
92                }
93            }
94            Condition::SendMessage(condition) => {
95                // If the vault sends a message to a coin, we must first validate the message's format, and then ensure
96                // that the corresponding coin spend is revealed and matches the delegated puzzle hash in the message.
97                // These coins are considered "linked", meaning that their conditions should also be treated as fact if
98                // they can be validated to be impossible to circumvent.
99                let vault_message = parse_vault_message(allocator, condition)?;
100                messages.push(vault_message);
101            }
102            Condition::Other(condition) => {
103                let (opcode, _) = <(BigInt, NodePtr)>::from_clvm(allocator, condition)?;
104
105                // If an unparseable opcode matches the SEND_MESSAGE opcode, we return an error.
106                // This is to make sure that we don't accidentally let a valid message slip through the cracks.
107                // If this happened, we wouldn't enforce the spend to be revealed, thus clear signing would be insecure.
108                if opcode == BigInt::from(SEND_MESSAGE) {
109                    return Err(DriverError::InvalidVaultMessageFormat);
110                }
111            }
112            _ => {}
113        }
114    }
115
116    Ok(VaultSpendSummary {
117        child,
118        drop_coins,
119        messages,
120    })
121}