chia_sdk_driver/primitives/
did.rs

1use chia_protocol::{Bytes32, Coin};
2use chia_puzzle_types::{
3    did::DidSolution,
4    singleton::{SingletonArgs, SingletonSolution},
5    LineageProof, Memos, Proof,
6};
7use chia_sdk_types::{run_puzzle, Condition, Conditions};
8use clvm_traits::{FromClvm, ToClvm};
9use clvm_utils::{tree_hash, ToTreeHash};
10use clvmr::{Allocator, NodePtr};
11
12use crate::{
13    DidLayer, DriverError, Layer, Puzzle, SingletonLayer, Spend, SpendContext, SpendWithConditions,
14};
15
16mod did_info;
17mod did_launcher;
18
19pub use did_info::*;
20
21/// Contains all information needed to spend the outer puzzles of DID coins.
22/// The [`DidInfo`] is used to construct the puzzle, but the [`Proof`] is needed for the solution.
23///
24/// The only thing missing to create a valid coin spend is the inner puzzle and solution.
25/// However, this is handled separately to provide as much flexibility as possible.
26///
27/// This type should contain all of the information you need to store in a database for later.
28/// As long as you can figure out what puzzle the p2 puzzle hash corresponds to and spend it,
29/// you have enough information to spend the DID coin.
30#[must_use]
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct Did<M> {
33    /// The coin that this [`Did`] represents. Its puzzle hash should match the [`DidInfo::puzzle_hash`].
34    pub coin: Coin,
35
36    /// The proof is needed by the singleton puzzle to prove that this coin is a legitimate singleton.
37    /// It's typically obtained by looking up and parsing the parent coin.
38    ///
39    /// Note that while the proof will be a [`LineageProof`] for most coins, for the first singleton
40    /// in the lineage it will be an [`EveProof`](chia_puzzle_types::EveProof) instead.
41    /// However, the eve coin is typically unhinted and spent in the same transaction as it was created,
42    /// so this is not relevant for database storage or syncing unspent coins.
43    pub proof: Proof,
44
45    /// The information needed to construct the outer puzzle of a DID. See [`DidInfo`] for more details.
46    pub info: DidInfo<M>,
47}
48
49impl<M> Did<M> {
50    pub fn new(coin: Coin, proof: Proof, info: DidInfo<M>) -> Self {
51        Self { coin, proof, info }
52    }
53
54    pub fn with_metadata<N>(self, metadata: N) -> Did<N> {
55        Did {
56            coin: self.coin,
57            proof: self.proof,
58            info: self.info.with_metadata(metadata),
59        }
60    }
61}
62
63impl<M> Did<M>
64where
65    M: ToTreeHash,
66{
67    /// Creates a [`LineageProof`] for which would be valid for any children created by this [`Did`].
68    pub fn child_lineage_proof(&self) -> LineageProof {
69        LineageProof {
70            parent_parent_coin_info: self.coin.parent_coin_info,
71            parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
72            parent_amount: self.coin.amount,
73        }
74    }
75
76    /// Creates a new [`Did`] that represents a child of this one.
77    pub fn child<N>(&self, p2_puzzle_hash: Bytes32, metadata: N, amount: u64) -> Did<N>
78    where
79        M: Clone,
80        N: ToTreeHash,
81    {
82        let mut info = self.info.clone().with_metadata(metadata);
83        info.p2_puzzle_hash = p2_puzzle_hash;
84        self.child_with(info, amount)
85    }
86
87    /// Creates a new [`Did`] that represents a child of this one.
88    ///
89    /// You can specify the [`DidInfo`] to use for the child manually.
90    /// In most cases, you will want to use [`Did::child`] instead.
91    ///
92    /// It's important to use the right [`DidInfo`] beforehand, otherwise
93    /// the puzzle hash of the child will not match the one expected by the coin.
94    pub fn child_with<N>(&self, info: DidInfo<N>, amount: u64) -> Did<N>
95    where
96        N: ToTreeHash,
97    {
98        Did::new(
99            Coin::new(
100                self.coin.coin_id(),
101                SingletonArgs::curry_tree_hash(info.launcher_id, info.inner_puzzle_hash()).into(),
102                amount,
103            ),
104            Proof::Lineage(self.child_lineage_proof()),
105            info,
106        )
107    }
108}
109
110impl<M> Did<M>
111where
112    M: ToClvm<Allocator> + FromClvm<Allocator> + ToTreeHash + Clone,
113{
114    /// Spends this DID coin with the provided inner spend.
115    /// The spend is added to the [`SpendContext`] for convenience.
116    pub fn spend(
117        &self,
118        ctx: &mut SpendContext,
119        inner_spend: Spend,
120    ) -> Result<Option<Self>, DriverError> {
121        let layers = self.info.clone().into_layers(inner_spend.puzzle);
122
123        let spend = layers.construct_spend(
124            ctx,
125            SingletonSolution {
126                lineage_proof: self.proof,
127                amount: self.coin.amount,
128                inner_solution: DidSolution::Spend(inner_spend.solution),
129            },
130        )?;
131
132        ctx.spend(self.coin, spend)?;
133
134        let output = ctx.run(inner_spend.puzzle, inner_spend.solution)?;
135        let conditions = Vec::<Condition>::from_clvm(ctx, output)?;
136
137        for condition in conditions {
138            if let Some(create_coin) = condition.into_create_coin() {
139                if create_coin.amount % 2 == 1 {
140                    let Memos::Some(memos) = create_coin.memos else {
141                        return Ok(None);
142                    };
143
144                    let Some((hint, _)) = <(Bytes32, NodePtr)>::from_clvm(ctx, memos).ok() else {
145                        return Ok(None);
146                    };
147
148                    let child = self.child(hint, self.info.metadata.clone(), create_coin.amount);
149
150                    if child.info.inner_puzzle_hash() == create_coin.puzzle_hash.into() {
151                        return Ok(Some(child));
152                    }
153                }
154            }
155        }
156
157        Ok(None)
158    }
159
160    /// Spends this DID coin with a [`Layer`] that supports [`SpendWithConditions`].
161    /// This is a building block for built in spend methods, but can also be used to spend
162    /// DID coins with conditions more easily.
163    ///
164    /// However, if you need full flexibility of the inner spend, you can use [`Did::spend`] instead.
165    pub fn spend_with<I>(
166        &self,
167        ctx: &mut SpendContext,
168        inner: &I,
169        conditions: Conditions,
170    ) -> Result<Option<Self>, DriverError>
171    where
172        I: SpendWithConditions,
173    {
174        let inner_spend = inner.spend_with_conditions(ctx, conditions)?;
175        self.spend(ctx, inner_spend)
176    }
177
178    /// Transfers this DID coin to a new p2 puzzle hash.
179    ///
180    /// This spend requires a [`Layer`] that supports [`SpendWithConditions`]. If it doesn't, you can
181    /// use [`Did::spend_with`] instead.
182    pub fn transfer<I>(
183        self,
184        ctx: &mut SpendContext,
185        inner: &I,
186        p2_puzzle_hash: Bytes32,
187        extra_conditions: Conditions,
188    ) -> Result<Did<M>, DriverError>
189    where
190        M: ToTreeHash,
191        I: SpendWithConditions,
192    {
193        let mut new_info = self.info.clone();
194        new_info.p2_puzzle_hash = p2_puzzle_hash;
195
196        let memos = ctx.hint(p2_puzzle_hash)?;
197
198        self.spend_with(
199            ctx,
200            inner,
201            extra_conditions.create_coin(
202                new_info.inner_puzzle_hash().into(),
203                self.coin.amount,
204                memos,
205            ),
206        )?;
207
208        let metadata = self.info.metadata.clone();
209
210        Ok(self.child(p2_puzzle_hash, metadata, self.coin.amount))
211    }
212
213    /// Updates the metadata of this DID.
214    ///
215    /// Because DID coins aren't wrapped automatically, and due to the way they are parsed in wallets,
216    /// an additional update spend is needed. This additional spend is not handled by this method, so
217    /// you will need to do it manually.
218    ///
219    /// This spend requires a [`Layer`] that supports [`SpendWithConditions`]. If it doesn't, you can
220    /// use [`Did::spend_with`] instead.
221    pub fn update_with_metadata<I, N>(
222        self,
223        ctx: &mut SpendContext,
224        inner: &I,
225        metadata: N,
226        extra_conditions: Conditions,
227    ) -> Result<Did<N>, DriverError>
228    where
229        I: SpendWithConditions,
230        M: ToTreeHash,
231        N: ToClvm<Allocator> + ToTreeHash + Clone,
232    {
233        let new_inner_puzzle_hash = self
234            .info
235            .clone()
236            .with_metadata(metadata.clone())
237            .inner_puzzle_hash();
238
239        let memos = ctx.hint(self.info.p2_puzzle_hash)?;
240
241        self.spend_with(
242            ctx,
243            inner,
244            extra_conditions.create_coin(new_inner_puzzle_hash.into(), self.coin.amount, memos),
245        )?;
246
247        Ok(self.child(self.info.p2_puzzle_hash, metadata, self.coin.amount))
248    }
249
250    /// Spends the DID without changing its metadata or p2 puzzle hash.
251    ///
252    /// This can be done to "settle" the DID's updated metadata and make it parseable by wallets.
253    /// It's also useful if you just want to emit conditions from the DID, without transferring it.
254    /// For example, when assigning a DID to one or more NFTs you can use an update spend to do so.
255    ///
256    /// This spend requires a [`Layer`] that supports [`SpendWithConditions`]. If it doesn't, you can
257    /// use [`Did::spend_with`] instead.
258    pub fn update<I>(
259        self,
260        ctx: &mut SpendContext,
261        inner: &I,
262        extra_conditions: Conditions,
263    ) -> Result<Did<M>, DriverError>
264    where
265        M: ToTreeHash,
266        I: SpendWithConditions,
267    {
268        let metadata = self.info.metadata.clone();
269        self.update_with_metadata(ctx, inner, metadata, extra_conditions)
270    }
271}
272
273impl<M> Did<M>
274where
275    M: ToClvm<Allocator> + FromClvm<Allocator> + Clone,
276{
277    /// Parses the child of an [`Did`] from the parent coin spend.
278    ///
279    /// This relies on the child being hinted and having the same metadata as the parent.
280    /// If this is not the case, the DID cannot be parsed or spent without additional context.
281    pub fn parse_child(
282        allocator: &mut Allocator,
283        parent_coin: Coin,
284        parent_puzzle: Puzzle,
285        parent_solution: NodePtr,
286        coin: Coin,
287    ) -> Result<Option<Self>, DriverError>
288    where
289        Self: Sized,
290    {
291        let Some(singleton_layer) =
292            SingletonLayer::<Puzzle>::parse_puzzle(allocator, parent_puzzle)?
293        else {
294            return Ok(None);
295        };
296
297        let Some(did_layer) =
298            DidLayer::<M, Puzzle>::parse_puzzle(allocator, singleton_layer.inner_puzzle)?
299        else {
300            return Ok(None);
301        };
302
303        if singleton_layer.launcher_id != did_layer.launcher_id {
304            return Err(DriverError::InvalidSingletonStruct);
305        }
306
307        let singleton_solution =
308            SingletonLayer::<Puzzle>::parse_solution(allocator, parent_solution)?;
309
310        let output = run_puzzle(
311            allocator,
312            singleton_layer.inner_puzzle.ptr(),
313            singleton_solution.inner_solution,
314        )?;
315        let conditions = Vec::<Condition>::from_clvm(allocator, output)?;
316
317        let Some(create_coin) = conditions
318            .into_iter()
319            .filter_map(Condition::into_create_coin)
320            .find(|create_coin| create_coin.amount % 2 == 1)
321        else {
322            return Err(DriverError::MissingChild);
323        };
324
325        let Memos::Some(memos) = create_coin.memos else {
326            return Err(DriverError::MissingHint);
327        };
328
329        let (hint, _) = <(Bytes32, NodePtr)>::from_clvm(allocator, memos)?;
330
331        let metadata_ptr = did_layer.metadata.to_clvm(allocator)?;
332        let metadata_hash = tree_hash(allocator, metadata_ptr);
333        let did_layer_hashed = did_layer.clone().with_metadata(metadata_hash);
334
335        let parent_inner_puzzle_hash = did_layer_hashed.tree_hash().into();
336        let layers = SingletonLayer::new(singleton_layer.launcher_id, did_layer);
337
338        let mut info = DidInfo::from_layers(layers);
339        info.p2_puzzle_hash = hint;
340
341        Ok(Some(Self {
342            coin,
343            proof: Proof::Lineage(LineageProof {
344                parent_parent_coin_info: parent_coin.parent_coin_info,
345                parent_inner_puzzle_hash,
346                parent_amount: parent_coin.amount,
347            }),
348            info,
349        }))
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use std::fmt;
356
357    use chia_protocol::Bytes32;
358    use chia_sdk_test::Simulator;
359    use clvm_traits::clvm_list;
360    use rstest::rstest;
361
362    use crate::{HashedPtr, Launcher, StandardLayer};
363
364    use super::*;
365
366    #[test]
367    fn test_create_and_update_simple_did() -> anyhow::Result<()> {
368        let mut sim = Simulator::new();
369        let ctx = &mut SpendContext::new();
370
371        let alice = sim.bls(1);
372        let alice_p2 = StandardLayer::new(alice.pk);
373
374        let launcher = Launcher::new(alice.coin.coin_id(), 1);
375        let (create_did, did) = launcher.create_simple_did(ctx, &alice_p2)?;
376        alice_p2.spend(ctx, alice.coin, create_did)?;
377        sim.spend_coins(ctx.take(), &[alice.sk])?;
378
379        assert_eq!(did.info.recovery_list_hash, None);
380        assert_eq!(did.info.num_verifications_required, 1);
381        assert_eq!(did.info.p2_puzzle_hash, alice.puzzle_hash);
382
383        Ok(())
384    }
385
386    #[rstest]
387    fn test_create_and_update_did(
388        #[values(None, Some(Bytes32::default()))] recovery_list_hash: Option<Bytes32>,
389        #[values(0, 1, 3)] num_verifications_required: u64,
390        #[values((), "Atom".to_string(), clvm_list!("Complex".to_string(), 42), 100)]
391        metadata: impl ToClvm<Allocator>
392            + FromClvm<Allocator>
393            + ToTreeHash
394            + Clone
395            + PartialEq
396            + fmt::Debug,
397    ) -> anyhow::Result<()> {
398        let mut sim = Simulator::new();
399        let ctx = &mut SpendContext::new();
400
401        let alice = sim.bls(1);
402        let alice_p2 = StandardLayer::new(alice.pk);
403
404        let launcher = Launcher::new(alice.coin.coin_id(), 1);
405        let (create_did, did) = launcher.create_did(
406            ctx,
407            recovery_list_hash,
408            num_verifications_required,
409            metadata.clone(),
410            &alice_p2,
411        )?;
412        alice_p2.spend(ctx, alice.coin, create_did)?;
413        sim.spend_coins(ctx.take(), &[alice.sk])?;
414
415        assert_eq!(did.info.recovery_list_hash, recovery_list_hash);
416        assert_eq!(
417            did.info.num_verifications_required,
418            num_verifications_required
419        );
420        assert_eq!(did.info.metadata, metadata);
421        assert_eq!(did.info.p2_puzzle_hash, alice.puzzle_hash);
422
423        Ok(())
424    }
425
426    #[test]
427    fn test_transfer_did() -> anyhow::Result<()> {
428        let mut sim = Simulator::new();
429        let ctx = &mut SpendContext::new();
430
431        let alice = sim.bls(1);
432        let alice_p2 = StandardLayer::new(alice.pk);
433
434        let (create_did, alice_did) =
435            Launcher::new(alice.coin.coin_id(), 1).create_simple_did(ctx, &alice_p2)?;
436        alice_p2.spend(ctx, alice.coin, create_did)?;
437
438        let bob = sim.bls(1);
439        let bob_p2 = StandardLayer::new(bob.pk);
440
441        let bob_did = alice_did.transfer(ctx, &alice_p2, bob.puzzle_hash, Conditions::new())?;
442        let did = bob_did.update(ctx, &bob_p2, Conditions::new())?;
443
444        assert_eq!(did.info.p2_puzzle_hash, bob.puzzle_hash);
445        assert_ne!(bob.puzzle_hash, alice.puzzle_hash);
446
447        sim.spend_coins(ctx.take(), &[alice.sk, bob.sk])?;
448
449        Ok(())
450    }
451
452    #[test]
453    fn test_update_did_metadata() -> anyhow::Result<()> {
454        let mut sim = Simulator::new();
455        let ctx = &mut SpendContext::new();
456
457        let alice = sim.bls(1);
458        let alice_p2 = StandardLayer::new(alice.pk);
459
460        let launcher = Launcher::new(alice.coin.coin_id(), 1);
461        let (create_did, did) = launcher.create_simple_did(ctx, &alice_p2)?;
462        alice_p2.spend(ctx, alice.coin, create_did)?;
463        sim.spend_coins(ctx.take(), &[alice.sk])?;
464
465        let new_metadata = "New Metadata".to_string();
466        let updated_did =
467            did.update_with_metadata(ctx, &alice_p2, new_metadata.clone(), Conditions::default())?;
468
469        assert_eq!(updated_did.info.metadata, new_metadata);
470
471        Ok(())
472    }
473
474    #[test]
475    fn test_nodeptr_metadata() -> anyhow::Result<()> {
476        let mut sim = Simulator::new();
477        let ctx = &mut SpendContext::new();
478
479        let alice = sim.bls(1);
480        let alice_p2 = StandardLayer::new(alice.pk);
481
482        let launcher = Launcher::new(alice.coin.coin_id(), 1);
483        let (create_did, did) = launcher.create_did(ctx, None, 1, HashedPtr::NIL, &alice_p2)?;
484        alice_p2.spend(ctx, alice.coin, create_did)?;
485        sim.spend_coins(ctx.take(), &[alice.sk])?;
486
487        let new_metadata = HashedPtr::from_ptr(ctx, ctx.one());
488        let updated_did =
489            did.update_with_metadata(ctx, &alice_p2, new_metadata, Conditions::default())?;
490
491        assert_eq!(updated_did.info.metadata, new_metadata);
492
493        Ok(())
494    }
495
496    #[test]
497    fn test_parse_did() -> anyhow::Result<()> {
498        let mut sim = Simulator::new();
499        let ctx = &mut SpendContext::new();
500
501        let alice = sim.bls(1);
502        let alice_p2 = StandardLayer::new(alice.pk);
503
504        let (create_did, expected_did) =
505            Launcher::new(alice.coin.coin_id(), 1).create_simple_did(ctx, &alice_p2)?;
506        alice_p2.spend(ctx, alice.coin, create_did)?;
507
508        sim.spend_coins(ctx.take(), &[alice.sk])?;
509
510        let mut allocator = Allocator::new();
511
512        let puzzle_reveal = sim
513            .puzzle_reveal(expected_did.coin.parent_coin_info)
514            .expect("missing puzzle")
515            .to_clvm(&mut allocator)?;
516
517        let solution = sim
518            .solution(expected_did.coin.parent_coin_info)
519            .expect("missing solution")
520            .to_clvm(&mut allocator)?;
521
522        let parent_coin = sim
523            .coin_state(expected_did.coin.parent_coin_info)
524            .expect("missing parent coin state")
525            .coin;
526
527        let puzzle = Puzzle::parse(&allocator, puzzle_reveal);
528
529        let did = Did::<()>::parse_child(
530            &mut allocator,
531            parent_coin,
532            puzzle,
533            solution,
534            expected_did.coin,
535        )?
536        .expect("could not parse did");
537
538        assert_eq!(did, expected_did);
539
540        Ok(())
541    }
542}