chia_sdk_driver/primitives/did/
did_info.rs

1use chia_protocol::Bytes32;
2use chia_puzzle_types::{did::DidArgs, singleton::SingletonStruct};
3use clvm_utils::{ToTreeHash, TreeHash};
4use clvmr::Allocator;
5
6use crate::{DidLayer, DriverError, HashedPtr, Layer, Puzzle, SingletonInfo, SingletonLayer};
7
8pub type StandardDidLayers<I> = SingletonLayer<DidLayer<HashedPtr, I>>;
9
10/// Information needed to construct the outer puzzle of a DID.
11/// It does not include the inner puzzle, which must be stored separately.
12///
13/// This type can be used on its own for parsing, or as part of the [`Did`](crate::Did) primitive.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct DidInfo {
16    /// The coin id of the launcher coin that created this DID's singleton.
17    pub launcher_id: Bytes32,
18
19    /// The hash of the recovery list. This is a very infrequently used feature
20    /// and is not fully supported at this time.
21    ///
22    /// In the Chia reference wallet, the recovery list hash must be present
23    /// even if recovery is disabled. However, in some other wallets it's allowed
24    /// to be [`None`]. This is an on-chain cost optimization and simplification.
25    pub recovery_list_hash: Option<Bytes32>,
26
27    /// The number of verifications required to recover the DID.
28    pub num_verifications_required: u64,
29
30    /// The metadata stored in the [`DidLayer`]. This can be updated freely,
31    /// but must be confirmed by an additional update spend to ensure wallets
32    /// can sync it from the parent coin.
33    pub metadata: HashedPtr,
34
35    /// The hash of the inner puzzle to this DID.
36    /// If you encode this puzzle hash as bech32m, it's the same as the current owner's address.
37    pub p2_puzzle_hash: Bytes32,
38}
39
40impl DidInfo {
41    pub fn new(
42        launcher_id: Bytes32,
43        recovery_list_hash: Option<Bytes32>,
44        num_verifications_required: u64,
45        metadata: HashedPtr,
46        p2_puzzle_hash: Bytes32,
47    ) -> Self {
48        Self {
49            launcher_id,
50            recovery_list_hash,
51            num_verifications_required,
52            metadata,
53            p2_puzzle_hash,
54        }
55    }
56
57    /// Parses a [`DidInfo`] from a [`Puzzle`] by extracting the [`DidLayer`].
58    ///
59    /// This will return a tuple of the [`DidInfo`] and its p2 puzzle.
60    ///
61    /// If the puzzle is not a DID, this will return [`None`] instead of an error.
62    /// However, if the puzzle should have been a DID but had a parsing error, this will return an error.
63    pub fn parse(
64        allocator: &Allocator,
65        puzzle: Puzzle,
66    ) -> Result<Option<(Self, Puzzle)>, DriverError> {
67        let Some(layers) = StandardDidLayers::<Puzzle>::parse_puzzle(allocator, puzzle)? else {
68            return Ok(None);
69        };
70
71        let p2_puzzle = layers.inner_puzzle.inner_puzzle;
72
73        Ok(Some((Self::from_layers(&layers), p2_puzzle)))
74    }
75
76    pub fn from_layers<I>(layers: &StandardDidLayers<I>) -> Self
77    where
78        I: ToTreeHash,
79    {
80        Self {
81            launcher_id: layers.launcher_id,
82            recovery_list_hash: layers.inner_puzzle.recovery_list_hash,
83            num_verifications_required: layers.inner_puzzle.num_verifications_required,
84            metadata: layers.inner_puzzle.metadata,
85            p2_puzzle_hash: layers.inner_puzzle.inner_puzzle.tree_hash().into(),
86        }
87    }
88
89    #[must_use]
90    pub fn into_layers<I>(self, p2_puzzle: I) -> StandardDidLayers<I> {
91        SingletonLayer::new(
92            self.launcher_id,
93            DidLayer::new(
94                self.launcher_id,
95                self.recovery_list_hash,
96                self.num_verifications_required,
97                self.metadata,
98                p2_puzzle,
99            ),
100        )
101    }
102}
103
104impl SingletonInfo for DidInfo {
105    fn launcher_id(&self) -> Bytes32 {
106        self.launcher_id
107    }
108
109    fn inner_puzzle_hash(&self) -> TreeHash {
110        DidArgs::curry_tree_hash(
111            self.p2_puzzle_hash.into(),
112            self.recovery_list_hash,
113            self.num_verifications_required,
114            SingletonStruct::new(self.launcher_id),
115            self.metadata.tree_hash(),
116        )
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use chia_sdk_test::Simulator;
123    use chia_sdk_types::Conditions;
124    use clvm_traits::ToClvm;
125
126    use crate::{Launcher, SpendContext, StandardLayer};
127
128    use super::*;
129
130    #[test]
131    fn test_parse_did_info() -> anyhow::Result<()> {
132        let mut sim = Simulator::new();
133        let ctx = &mut SpendContext::new();
134
135        let alice = sim.bls(1);
136        let alice_p2 = StandardLayer::new(alice.pk);
137
138        let custom_metadata = ctx.alloc_hashed(&["Metadata".to_string(), "Example".to_string()])?;
139        let (create_did, did) = Launcher::new(alice.coin.coin_id(), 1).create_did(
140            ctx,
141            None,
142            1,
143            custom_metadata,
144            &alice_p2,
145        )?;
146        alice_p2.spend(ctx, alice.coin, create_did)?;
147
148        let original_did = did;
149        let _did = did.update(ctx, &alice_p2, Conditions::new())?;
150
151        sim.spend_coins(ctx.take(), &[alice.sk])?;
152
153        let puzzle_reveal = sim
154            .puzzle_reveal(original_did.coin.coin_id())
155            .expect("missing did puzzle");
156
157        let mut allocator = Allocator::new();
158        let ptr = puzzle_reveal.to_clvm(&mut allocator)?;
159        let puzzle = Puzzle::parse(&allocator, ptr);
160        let (did_info, p2_puzzle) = DidInfo::parse(&allocator, puzzle)?.expect("not a did");
161
162        assert_eq!(did_info, original_did.info);
163        assert_eq!(p2_puzzle.curried_puzzle_hash(), alice.puzzle_hash.into());
164
165        Ok(())
166    }
167}