1use chia_protocol::{Bytes32, Coin};
14use chia_puzzle_types::standard::StandardArgs;
15use chia_wallet_sdk::driver::{Did, HashedPtr, Launcher, SingletonInfo, SpendContext};
16use chia_wallet_sdk::types::Conditions;
17use clvm_utils::tree_hash;
18
19use crate::context::{drain_coin_spends, inner_spend};
20use crate::error::DidResult;
21use crate::types::{DidSpend, Owner};
22
23pub fn create_did(
49 ctx: &mut SpendContext,
50 funding_coin: Coin,
51 owner: Owner,
52 recovery_list_hash: Option<Bytes32>,
53 num_verifications_required: u64,
54 metadata: HashedPtr,
55) -> DidResult<DidSpend> {
56 let owner_puzzle_hash = owner_puzzle_hash(ctx, owner)?;
57
58 let launcher = Launcher::new(funding_coin.coin_id(), funding_coin.amount);
59 let (launch_conditions, eve) = launcher.create_eve_did(
60 ctx,
61 owner_puzzle_hash,
62 recovery_list_hash,
63 num_verifications_required,
64 metadata,
65 )?;
66
67 let settled = settle(ctx, eve, owner)?;
68 spend_funding_coin(ctx, funding_coin, owner, launch_conditions)?;
69
70 Ok(DidSpend::new(drain_coin_spends(ctx), Some(settled)))
71}
72
73pub fn create_simple_did(
80 ctx: &mut SpendContext,
81 funding_coin: Coin,
82 owner: Owner,
83) -> DidResult<DidSpend> {
84 create_did(ctx, funding_coin, owner, None, 1, HashedPtr::NIL)
85}
86
87pub fn create_eve_did_only(
102 ctx: &mut SpendContext,
103 funding_coin: Coin,
104 owner: Owner,
105 recovery_list_hash: Option<Bytes32>,
106 num_verifications_required: u64,
107 metadata: HashedPtr,
108) -> DidResult<DidSpend> {
109 let owner_puzzle_hash = owner_puzzle_hash(ctx, owner)?;
110
111 let launcher = Launcher::new(funding_coin.coin_id(), funding_coin.amount);
112 let (launch_conditions, eve) = launcher.create_eve_did(
113 ctx,
114 owner_puzzle_hash,
115 recovery_list_hash,
116 num_verifications_required,
117 metadata,
118 )?;
119
120 spend_funding_coin(ctx, funding_coin, owner, launch_conditions)?;
121
122 Ok(DidSpend::new(drain_coin_spends(ctx), Some(eve)))
123}
124
125fn owner_puzzle_hash(ctx: &SpendContext, owner: Owner) -> DidResult<Bytes32> {
130 Ok(match owner {
131 Owner::Standard(public_key) => StandardArgs::curry_tree_hash(public_key).into(),
132 Owner::Custom(spend) => tree_hash(ctx, spend.puzzle).into(),
133 })
134}
135
136fn settle(ctx: &mut SpendContext, did: Did, owner: Owner) -> DidResult<Did> {
140 let unchanged_inner_puzzle_hash: Bytes32 = did.info.inner_puzzle_hash().into();
141 let memos = ctx.hint(did.info.p2_puzzle_hash)?;
142 let settle_conditions =
143 Conditions::new().create_coin(unchanged_inner_puzzle_hash, did.coin.amount, memos);
144
145 let spend = inner_spend(ctx, owner, settle_conditions)?;
146 did.spend(ctx, spend)?.ok_or_else(|| {
147 crate::error::DidError::Parse("settle spend produced no successor DID".into())
148 })
149}
150
151fn spend_funding_coin(
154 ctx: &mut SpendContext,
155 funding_coin: Coin,
156 owner: Owner,
157 launch_conditions: Conditions,
158) -> DidResult<()> {
159 let spend = inner_spend(ctx, owner, launch_conditions)?;
160 ctx.spend(funding_coin, spend)?;
161 Ok(())
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use chia_wallet_sdk::prelude::MAINNET_CONSTANTS;
168 use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature};
169 use chia_wallet_sdk::test::Simulator;
170
171 #[test]
174 fn create_simple_did_produces_a_spendable_settled_did() -> anyhow::Result<()> {
175 let mut sim = Simulator::new();
176 let ctx = &mut SpendContext::new();
177
178 let owner = sim.bls(1);
179 let spend = create_simple_did(ctx, owner.coin, Owner::Standard(owner.pk))?;
180
181 let child = spend.child.expect("create always returns a child DID");
182 assert_eq!(child.info.recovery_list_hash, None);
183 assert_eq!(child.info.num_verifications_required, 1);
184 assert_eq!(child.info.p2_puzzle_hash, owner.puzzle_hash);
185
186 sim.spend_coins(spend.coin_spends, &[owner.sk])?;
187 Ok(())
188 }
189
190 #[test]
195 fn create_did_requires_two_agg_sig_mes_over_the_owner_key() -> anyhow::Result<()> {
196 let mut sim = Simulator::new();
197 let ctx = &mut SpendContext::new();
198
199 let owner = sim.bls(1);
200 let spend = create_simple_did(ctx, owner.coin, Owner::Standard(owner.pk))?;
201
202 let constants = AggSigConstants::from(&*MAINNET_CONSTANTS);
203 let required = crate::sign::required_signatures(&spend.coin_spends, &constants)
204 .expect("signature calculation must succeed for a well-formed create spend");
205
206 assert_eq!(
207 required.len(),
208 2,
209 "the funding-coin spend AND the settle spend each require one AGG_SIG_ME"
210 );
211 for signature in &required {
212 match signature {
213 RequiredSignature::Bls(bls) => assert_eq!(bls.public_key, owner.pk),
214 RequiredSignature::Secp(_) => panic!("a standard owner signs with BLS, not secp"),
215 }
216 }
217 Ok(())
218 }
219
220 #[test]
222 fn create_did_preserves_a_custom_recovery_configuration() -> anyhow::Result<()> {
223 let mut sim = Simulator::new();
224 let ctx = &mut SpendContext::new();
225
226 let owner = sim.bls(1);
227 let recovery_list_hash =
228 Some(clvm_utils::tree_hash_atom(b"dig-did::create::recovery-list").into());
229
230 let spend = create_did(
231 ctx,
232 owner.coin,
233 Owner::Standard(owner.pk),
234 recovery_list_hash,
235 2,
236 HashedPtr::NIL,
237 )?;
238 let child = spend.child.expect("create always returns a child DID");
239
240 assert_eq!(child.info.recovery_list_hash, recovery_list_hash);
241 assert_eq!(child.info.num_verifications_required, 2);
242
243 sim.spend_coins(spend.coin_spends, &[owner.sk])?;
244 Ok(())
245 }
246
247 #[test]
250 fn create_eve_did_only_skips_the_settle_spend() -> anyhow::Result<()> {
251 let mut sim = Simulator::new();
252 let ctx = &mut SpendContext::new();
253
254 let owner = sim.bls(1);
255 let spend = create_eve_did_only(
256 ctx,
257 owner.coin,
258 Owner::Standard(owner.pk),
259 None,
260 1,
261 HashedPtr::NIL,
262 )?;
263
264 assert_eq!(spend.coin_spends.len(), 2);
266
267 let eve = spend.child.expect("create always returns a child DID");
268 assert_eq!(eve.info.p2_puzzle_hash, owner.puzzle_hash);
269
270 sim.spend_coins(spend.coin_spends, &[owner.sk])?;
271 Ok(())
272 }
273}