chia_sdk_driver/layers/action_layer/
verification_layer.rs

1use std::fmt::Debug;
2
3use chia_protocol::Bytes32;
4use chia_puzzle_types::singleton::SingletonStruct;
5use chia_puzzles::{SINGLETON_LAUNCHER_HASH, SINGLETON_TOP_LAYER_V1_1_HASH};
6use chia_sdk_types::puzzles::{
7    CatNftMetadata, VerificationLayer1stCurryArgs, VerificationLayer2ndCurryArgs,
8    VerificationLayerSolution, VERIFICATION_LAYER_PUZZLE_HASH,
9};
10use clvm_traits::{clvm_list, FromClvm, ToClvm};
11use clvm_utils::{CurriedProgram, ToTreeHash};
12use clvmr::{Allocator, NodePtr};
13
14use crate::{DriverError, Layer, Puzzle, SpendContext};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct VerificationLayer {
18    pub revocation_singleton_launcher_id: Bytes32,
19    pub verified_data: VerifiedData,
20}
21
22impl VerificationLayer {
23    pub fn new(revocation_singleton_launcher_id: Bytes32, verified_data: VerifiedData) -> Self {
24        Self {
25            revocation_singleton_launcher_id,
26            verified_data,
27        }
28    }
29}
30
31impl Layer for VerificationLayer {
32    type Solution = VerificationLayerSolution;
33
34    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
35        let Some(puzzle_2nd_curry) = puzzle.as_curried() else {
36            return Ok(None);
37        };
38
39        let puzzle_2nd_curry =
40            CurriedProgram::<NodePtr, NodePtr>::from_clvm(allocator, puzzle_2nd_curry.curried_ptr)?;
41        let puzzle_1st_curry = Puzzle::parse(allocator, puzzle_2nd_curry.program);
42        let Some(puzzle_1st_curry) = puzzle_1st_curry.as_curried() else {
43            return Ok(None);
44        };
45
46        if puzzle_1st_curry.mod_hash != VERIFICATION_LAYER_PUZZLE_HASH {
47            return Ok(None);
48        }
49
50        let args_2nd_curry = VerificationLayer2ndCurryArgs::<VerifiedData>::from_clvm(
51            allocator,
52            puzzle_2nd_curry.args,
53        )?;
54        let args_1st_curry =
55            VerificationLayer1stCurryArgs::from_clvm(allocator, puzzle_1st_curry.args)?;
56
57        if args_1st_curry
58            .revocation_singleton_struct
59            .launcher_puzzle_hash
60            != SINGLETON_LAUNCHER_HASH.into()
61            || args_1st_curry.revocation_singleton_struct.mod_hash
62                != SINGLETON_TOP_LAYER_V1_1_HASH.into()
63        {
64            return Err(DriverError::NonStandardLayer);
65        }
66
67        if args_2nd_curry.self_hash
68            != VerificationLayer1stCurryArgs::curry_tree_hash(
69                args_1st_curry.revocation_singleton_struct.launcher_id,
70            )
71            .into()
72        {
73            return Err(DriverError::NonStandardLayer);
74        }
75
76        Ok(Some(Self {
77            revocation_singleton_launcher_id: args_1st_curry
78                .revocation_singleton_struct
79                .launcher_id,
80            verified_data: args_2nd_curry.verified_data,
81        }))
82    }
83
84    fn parse_solution(
85        allocator: &Allocator,
86        solution: NodePtr,
87    ) -> Result<Self::Solution, DriverError> {
88        VerificationLayerSolution::from_clvm(allocator, solution).map_err(DriverError::FromClvm)
89    }
90
91    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
92        let puzzle_1st_curry = ctx.curry(VerificationLayer1stCurryArgs {
93            revocation_singleton_struct: SingletonStruct::new(
94                self.revocation_singleton_launcher_id,
95            ),
96        })?;
97        let self_hash =
98            VerificationLayer1stCurryArgs::curry_tree_hash(self.revocation_singleton_launcher_id)
99                .into();
100
101        CurriedProgram {
102            program: puzzle_1st_curry,
103            args: VerificationLayer2ndCurryArgs {
104                self_hash,
105                verified_data: self.verified_data.clone(),
106            },
107        }
108        .to_clvm(ctx)
109        .map_err(DriverError::ToClvm)
110    }
111
112    fn construct_solution(
113        &self,
114        ctx: &mut SpendContext,
115        solution: Self::Solution,
116    ) -> Result<NodePtr, DriverError> {
117        ctx.alloc(&solution)
118    }
119}
120
121#[derive(ToClvm, FromClvm, Debug, Clone, PartialEq, Eq)]
122#[clvm(list)]
123pub struct VerifiedData {
124    pub version: u32,
125    pub asset_id: Bytes32,
126    pub data_hash: Bytes32,
127    #[clvm(rest)]
128    pub comment: String,
129}
130
131impl VerifiedData {
132    pub fn data_hash_from_cat_nft_metadata(metadata: &CatNftMetadata) -> Bytes32 {
133        clvm_list!(
134            metadata.ticker.clone(),
135            metadata.name.clone(),
136            metadata.description.clone(),
137            metadata.image_hash,
138            metadata.metadata_hash,
139            metadata.license_hash,
140        )
141        .tree_hash()
142        .into()
143    }
144
145    pub fn from_cat_nft_metadata(
146        asset_id: Bytes32,
147        metadata: &CatNftMetadata,
148        comment: String,
149    ) -> Self {
150        Self {
151            version: 1,
152            asset_id,
153            data_hash: Self::data_hash_from_cat_nft_metadata(metadata),
154            comment,
155        }
156    }
157
158    pub fn get_hint(&self) -> Bytes32 {
159        self.data_hash
160    }
161}