use chia_protocol::{Bytes32, Coin};
use chia_puzzle_types::standard::StandardArgs;
use chia_wallet_sdk::driver::{Did, HashedPtr, Launcher, SingletonInfo, SpendContext};
use chia_wallet_sdk::types::Conditions;
use clvm_utils::tree_hash;
use crate::context::{drain_coin_spends, inner_spend};
use crate::error::DidResult;
use crate::types::{DidSpend, Owner};
pub fn create_did(
ctx: &mut SpendContext,
funding_coin: Coin,
owner: Owner,
recovery_list_hash: Option<Bytes32>,
num_verifications_required: u64,
metadata: HashedPtr,
) -> DidResult<DidSpend> {
let owner_puzzle_hash = owner_puzzle_hash(ctx, owner)?;
let launcher = Launcher::new(funding_coin.coin_id(), funding_coin.amount);
let (launch_conditions, eve) = launcher.create_eve_did(
ctx,
owner_puzzle_hash,
recovery_list_hash,
num_verifications_required,
metadata,
)?;
let settled = settle(ctx, eve, owner)?;
spend_funding_coin(ctx, funding_coin, owner, launch_conditions)?;
Ok(DidSpend::new(drain_coin_spends(ctx), Some(settled)))
}
pub fn create_simple_did(
ctx: &mut SpendContext,
funding_coin: Coin,
owner: Owner,
) -> DidResult<DidSpend> {
create_did(ctx, funding_coin, owner, None, 1, HashedPtr::NIL)
}
pub fn create_eve_did_only(
ctx: &mut SpendContext,
funding_coin: Coin,
owner: Owner,
recovery_list_hash: Option<Bytes32>,
num_verifications_required: u64,
metadata: HashedPtr,
) -> DidResult<DidSpend> {
let owner_puzzle_hash = owner_puzzle_hash(ctx, owner)?;
let launcher = Launcher::new(funding_coin.coin_id(), funding_coin.amount);
let (launch_conditions, eve) = launcher.create_eve_did(
ctx,
owner_puzzle_hash,
recovery_list_hash,
num_verifications_required,
metadata,
)?;
spend_funding_coin(ctx, funding_coin, owner, launch_conditions)?;
Ok(DidSpend::new(drain_coin_spends(ctx), Some(eve)))
}
fn owner_puzzle_hash(ctx: &SpendContext, owner: Owner) -> DidResult<Bytes32> {
Ok(match owner {
Owner::Standard(public_key) => StandardArgs::curry_tree_hash(public_key).into(),
Owner::Custom(spend) => tree_hash(ctx, spend.puzzle).into(),
})
}
fn settle(ctx: &mut SpendContext, did: Did, owner: Owner) -> DidResult<Did> {
let unchanged_inner_puzzle_hash: Bytes32 = did.info.inner_puzzle_hash().into();
let memos = ctx.hint(did.info.p2_puzzle_hash)?;
let settle_conditions =
Conditions::new().create_coin(unchanged_inner_puzzle_hash, did.coin.amount, memos);
let spend = inner_spend(ctx, owner, settle_conditions)?;
did.spend(ctx, spend)?.ok_or_else(|| {
crate::error::DidError::Parse("settle spend produced no successor DID".into())
})
}
fn spend_funding_coin(
ctx: &mut SpendContext,
funding_coin: Coin,
owner: Owner,
launch_conditions: Conditions,
) -> DidResult<()> {
let spend = inner_spend(ctx, owner, launch_conditions)?;
ctx.spend(funding_coin, spend)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use chia_wallet_sdk::prelude::MAINNET_CONSTANTS;
use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature};
use chia_wallet_sdk::test::Simulator;
#[test]
fn create_simple_did_produces_a_spendable_settled_did() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let ctx = &mut SpendContext::new();
let owner = sim.bls(1);
let spend = create_simple_did(ctx, owner.coin, Owner::Standard(owner.pk))?;
let child = spend.child.expect("create always returns a child DID");
assert_eq!(child.info.recovery_list_hash, None);
assert_eq!(child.info.num_verifications_required, 1);
assert_eq!(child.info.p2_puzzle_hash, owner.puzzle_hash);
sim.spend_coins(spend.coin_spends, &[owner.sk])?;
Ok(())
}
#[test]
fn create_did_requires_two_agg_sig_mes_over_the_owner_key() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let ctx = &mut SpendContext::new();
let owner = sim.bls(1);
let spend = create_simple_did(ctx, owner.coin, Owner::Standard(owner.pk))?;
let constants = AggSigConstants::from(&*MAINNET_CONSTANTS);
let required = crate::sign::required_signatures(&spend.coin_spends, &constants)
.expect("signature calculation must succeed for a well-formed create spend");
assert_eq!(
required.len(),
2,
"the funding-coin spend AND the settle spend each require one AGG_SIG_ME"
);
for signature in &required {
match signature {
RequiredSignature::Bls(bls) => assert_eq!(bls.public_key, owner.pk),
RequiredSignature::Secp(_) => panic!("a standard owner signs with BLS, not secp"),
}
}
Ok(())
}
#[test]
fn create_did_preserves_a_custom_recovery_configuration() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let ctx = &mut SpendContext::new();
let owner = sim.bls(1);
let recovery_list_hash =
Some(clvm_utils::tree_hash_atom(b"dig-did::create::recovery-list").into());
let spend = create_did(
ctx,
owner.coin,
Owner::Standard(owner.pk),
recovery_list_hash,
2,
HashedPtr::NIL,
)?;
let child = spend.child.expect("create always returns a child DID");
assert_eq!(child.info.recovery_list_hash, recovery_list_hash);
assert_eq!(child.info.num_verifications_required, 2);
sim.spend_coins(spend.coin_spends, &[owner.sk])?;
Ok(())
}
#[test]
fn create_eve_did_only_skips_the_settle_spend() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let ctx = &mut SpendContext::new();
let owner = sim.bls(1);
let spend = create_eve_did_only(
ctx,
owner.coin,
Owner::Standard(owner.pk),
None,
1,
HashedPtr::NIL,
)?;
assert_eq!(spend.coin_spends.len(), 2);
let eve = spend.child.expect("create always returns a child DID");
assert_eq!(eve.info.p2_puzzle_hash, owner.puzzle_hash);
sim.spend_coins(spend.coin_spends, &[owner.sk])?;
Ok(())
}
}