chia_sdk_driver/primitives/action_layer/
verification.rs

1use chia_protocol::{Bytes32, Coin, CoinSpend};
2use chia_puzzle_types::singleton::{SingletonArgs, SingletonSolution};
3use chia_puzzle_types::{EveProof, LineageProof, Proof};
4use chia_puzzles::SINGLETON_LAUNCHER_HASH;
5use chia_sdk_types::puzzles::{VerificationLayer2ndCurryArgs, VerificationLayerSolution};
6use clvm_traits::{FromClvm, ToClvm};
7use clvm_utils::{ToTreeHash, TreeHash};
8use clvmr::{Allocator, NodePtr};
9
10use crate::{
11    DriverError, Layer, Puzzle, SingletonLayer, SpendContext, VerificationLayer, VerifiedData,
12};
13
14use super::VerificationInfo;
15
16type VerificationLayers = SingletonLayer<VerificationLayer>;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[must_use]
20pub struct Verification {
21    pub coin: Coin,
22    pub proof: Proof,
23
24    pub info: VerificationInfo,
25}
26
27impl Verification {
28    pub fn new(coin: Coin, proof: Proof, info: VerificationInfo) -> Self {
29        Self { coin, proof, info }
30    }
31
32    pub fn after_mint(
33        launcher_parent: Bytes32,
34        revocation_singleton_launcher_id: Bytes32,
35        verified_data: VerifiedData,
36    ) -> Self {
37        let launcher_coin = Coin::new(launcher_parent, SINGLETON_LAUNCHER_HASH.into(), 0);
38        let verification_launcher_id = launcher_coin.coin_id();
39
40        let info = VerificationInfo {
41            launcher_id: verification_launcher_id,
42            revocation_singleton_launcher_id,
43            verified_data,
44        };
45
46        Self {
47            coin: Coin::new(verification_launcher_id, Self::puzzle_hash(&info).into(), 1),
48            proof: Proof::Eve(EveProof {
49                parent_parent_coin_info: launcher_parent,
50                parent_amount: 0,
51            }),
52            info,
53        }
54    }
55
56    pub fn inner_puzzle_hash<T>(
57        revocation_singleton_launcher_id: Bytes32,
58        verified_data: &T,
59    ) -> TreeHash
60    where
61        T: ToTreeHash,
62    {
63        VerificationLayer2ndCurryArgs::curry_tree_hash(
64            revocation_singleton_launcher_id,
65            verified_data,
66        )
67    }
68
69    pub fn puzzle_hash(info: &VerificationInfo) -> TreeHash {
70        SingletonArgs::curry_tree_hash(
71            info.launcher_id,
72            Self::inner_puzzle_hash(
73                info.revocation_singleton_launcher_id,
74                &info.verified_data.tree_hash(),
75            ),
76        )
77    }
78
79    pub fn into_layers(self) -> VerificationLayers {
80        SingletonLayer::new(
81            self.info.launcher_id,
82            VerificationLayer::new(
83                self.info.revocation_singleton_launcher_id,
84                self.info.verified_data,
85            ),
86        )
87    }
88
89    pub fn into_layers_with_clone(&self) -> VerificationLayers {
90        SingletonLayer::new(
91            self.info.launcher_id,
92            VerificationLayer::new(
93                self.info.revocation_singleton_launcher_id,
94                self.info.verified_data.clone(),
95            ),
96        )
97    }
98
99    pub fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
100        let layers = self.into_layers_with_clone();
101
102        layers.construct_puzzle(ctx)
103    }
104
105    pub fn spend(
106        self,
107        ctx: &mut SpendContext,
108        revocation_singleton_inner_puzzle_hash: Option<Bytes32>,
109    ) -> Result<(), DriverError> {
110        let sol = SingletonSolution {
111            lineage_proof: self.proof,
112            amount: self.coin.amount,
113            inner_solution: VerificationLayerSolution {
114                revocation_singleton_inner_puzzle_hash,
115            },
116        };
117        let my_coin = self.coin;
118
119        let layers = self.into_layers();
120
121        let puzzle_reveal = layers.construct_puzzle(ctx)?;
122        let solution = layers.construct_solution(ctx, sol)?;
123
124        let puzzle_reveal = ctx.serialize(&puzzle_reveal)?;
125        let solution = ctx.serialize(&solution)?;
126
127        ctx.insert(CoinSpend::new(my_coin, puzzle_reveal, solution));
128
129        Ok(())
130    }
131}
132
133impl Verification {
134    pub fn from_parent_spend(
135        allocator: &mut Allocator,
136        parent_coin: Coin,
137        parent_puzzle: Puzzle,
138    ) -> Result<Option<Self>, DriverError> {
139        let Some(parent_layers) = VerificationLayers::parse_puzzle(allocator, parent_puzzle)?
140        else {
141            return Ok(None);
142        };
143
144        let parent_inner_puzzle_hash = Verification::inner_puzzle_hash(
145            parent_layers.inner_puzzle.revocation_singleton_launcher_id,
146            &parent_layers.inner_puzzle.verified_data.tree_hash(),
147        )
148        .into();
149
150        Ok(Some(Self {
151            coin: Coin::new(parent_coin.coin_id(), parent_coin.puzzle_hash, 1),
152            proof: Proof::Lineage(LineageProof {
153                parent_parent_coin_info: parent_coin.parent_coin_info,
154                parent_inner_puzzle_hash,
155                parent_amount: parent_coin.amount,
156            }),
157            info: VerificationInfo::new(
158                parent_layers.launcher_id,
159                parent_layers.inner_puzzle.revocation_singleton_launcher_id,
160                parent_layers.inner_puzzle.verified_data,
161            ),
162        }))
163    }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)]
167#[clvm(list)]
168pub struct VerificationLauncherKVList {
169    pub revocation_singleton_launcher_id: Bytes32,
170    pub verified_data: VerifiedData,
171}
172
173#[cfg(test)]
174mod tests {
175    use anyhow::Ok;
176    use chia_protocol::Bytes;
177    use chia_puzzle_types::Memos;
178    use chia_puzzles::SINGLETON_LAUNCHER_HASH;
179    use chia_sdk_test::Simulator;
180    use chia_sdk_types::Conditions;
181
182    use crate::{Launcher, SingletonInfo, StandardLayer, VerificationAsserter, VerifiedData};
183
184    use super::*;
185
186    #[test]
187    fn test_verifications_and_asserter() -> anyhow::Result<()> {
188        let mut sim = Simulator::new();
189        let ctx = &mut SpendContext::new();
190        let bls = sim.bls(1);
191        let p2 = StandardLayer::new(bls.pk);
192
193        let did_launcher = Launcher::new(bls.coin.coin_id(), 1);
194        let (create_did, did) = did_launcher.create_simple_did(ctx, &p2)?;
195        p2.spend(ctx, bls.coin, create_did)?;
196
197        let verifier_proof = did.child_lineage_proof();
198        let did = did.update(
199            ctx,
200            &p2,
201            Conditions::new().create_coin(SINGLETON_LAUNCHER_HASH.into(), 0, Memos::None),
202        )?;
203        let verification_launcher = Launcher::new(did.coin.parent_coin_info, 0);
204        // we don't need an extra mojo for the verification coin since it's melted in the same tx
205
206        let verified_data = VerifiedData {
207            version: 1,
208            asset_id: Bytes32::new([2; 32]),
209            data_hash: Bytes32::new([3; 32]),
210            comment: "Test verification for test testing purposes only.".to_string(),
211        };
212        let verification = Verification::after_mint(
213            verification_launcher.coin().parent_coin_info,
214            did.info.launcher_id,
215            verified_data.clone(),
216        );
217
218        let (_conds, new_coin) = verification_launcher.with_singleton_amount(1).spend(
219            ctx,
220            Verification::inner_puzzle_hash(
221                verification.info.revocation_singleton_launcher_id,
222                &verification.info.verified_data,
223            )
224            .into(),
225            (),
226        )?;
227
228        assert_eq!(new_coin, verification.coin);
229
230        // spend the verification coin in oracle mode
231        verification.clone().spend(ctx, None)?;
232
233        let parent_puzzle = verification.construct_puzzle(ctx)?;
234        let parent_puzzle = Puzzle::parse(ctx, parent_puzzle);
235        let verification =
236            Verification::from_parent_spend(ctx, verification.coin, parent_puzzle)?.unwrap();
237
238        // create verification payment and spend it
239        let verification_asserter = VerificationAsserter::from(
240            did.info.launcher_id,
241            verified_data.version,
242            verified_data.asset_id.tree_hash(),
243            verified_data.data_hash.tree_hash(),
244        );
245
246        let payment_coin = sim.new_coin(verification_asserter.tree_hash().into(), 1337);
247        verification_asserter.spend(ctx, payment_coin, verifier_proof, 0, verified_data.comment)?;
248
249        // melt verification coin
250        let revocation_singleton_inner_ph = did.info.inner_puzzle_hash().into();
251
252        let msg_data = ctx.alloc(&verification.coin.puzzle_hash)?;
253        let _ = did.update(
254            ctx,
255            &p2,
256            Conditions::new().send_message(18, Bytes::default(), vec![msg_data]),
257        )?;
258
259        verification.spend(ctx, Some(revocation_singleton_inner_ph))?;
260
261        sim.spend_coins(ctx.take(), &[bls.sk])?;
262
263        Ok(())
264    }
265}