use bitcoin::blockdata::constants::ChainHash;
use bitcoin::key::rand::{thread_rng, Rng};
use bitcoin::psbt::Psbt;
use bitcoin::{Amount, Network, ScriptBuf, Transaction};
use ddk_dlc::secp256k1_zkp::{PublicKey, SecretKey};
use ddk_dlc::DlcTransactions;
use ddk_messages::contract_msgs::ContractInfo;
use ddk_messages::{AcceptDlc, FundingInput, SignDlc};
use super::error::ContractError;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Party {
Offer,
Accept,
}
#[derive(Clone, Debug)]
pub struct PartyParams {
pub funding_pubkey: PublicKey,
pub funding_inputs: Vec<FundingInput>,
pub payout_spk: ScriptBuf,
pub payout_serial_id: Option<u64>,
pub change_spk: ScriptBuf,
pub change_serial_id: Option<u64>,
}
#[derive(Clone, Debug)]
pub struct CreateOfferParams {
pub chain_hash: [u8; 32],
pub temporary_contract_id: Option<[u8; 32]>,
pub contract_info: ContractInfo,
pub offer_collateral: Amount,
pub party: PartyParams,
pub fund_output_serial_id: Option<u64>,
pub fee_rate_per_vb: u64,
pub cet_locktime: u32,
pub refund_locktime: u32,
pub contract_flags: u8,
}
#[derive(Clone, Debug)]
pub struct AcceptOfferParams {
pub party: PartyParams,
pub min_timeout_interval: u32,
pub max_timeout_interval: u32,
}
pub struct AcceptResult {
pub accept: AcceptDlc,
pub transactions: DlcTransactions,
pub funding_psbt: Psbt,
}
pub struct SignResult {
pub sign: SignDlc,
pub transactions: DlcTransactions,
}
#[derive(Clone, Debug)]
pub struct InputDerivation {
pub input_serial_id: u64,
pub derivation_path: bitcoin::bip32::DerivationPath,
}
#[derive(Clone, Debug)]
pub struct DescriptorInput {
pub input_serial_id: u64,
pub derivation_index: u32,
}
#[derive(Clone, Debug)]
pub struct DlcInputSigningKey {
pub input_serial_id: u64,
pub prior_funding_secret_key: SecretKey,
}
pub fn funding_input(
previous_transaction: &Transaction,
vout: u32,
input_serial_id: Option<u64>,
sequence: u32,
max_witness_len: u16,
redeem_script: ScriptBuf,
) -> Result<FundingInput, ContractError> {
let prevout = previous_transaction
.output
.get(vout as usize)
.ok_or_else(|| {
ContractError::InvalidFundingInput(format!("previous output {vout} does not exist"))
})?;
if prevout.script_pubkey.is_p2sh() {
if redeem_script.is_empty() {
return Err(ContractError::InvalidFundingInput(
"P2SH input requires a redeem script".to_string(),
));
}
if ScriptBuf::new_p2sh(&redeem_script.script_hash()) != prevout.script_pubkey {
return Err(ContractError::InvalidFundingInput(
"redeem script does not match the P2SH script pubkey".to_string(),
));
}
} else if !redeem_script.is_empty() {
return Err(ContractError::InvalidFundingInput(
"redeem script provided for a non-P2SH input".to_string(),
));
}
Ok(FundingInput {
input_serial_id: input_serial_id.unwrap_or_else(random_serial_id),
prev_tx: bitcoin::consensus::serialize(previous_transaction),
prev_tx_vout: vout,
sequence,
max_witness_len,
redeem_script,
dlc_input: None,
})
}
pub fn chain_hash_from_network(network: Network) -> [u8; 32] {
ChainHash::using_genesis_block_const(network).to_bytes()
}
pub(crate) fn network_from_chain_hash(chain_hash: [u8; 32]) -> Option<Network> {
[
Network::Bitcoin,
Network::Testnet,
Network::Signet,
Network::Regtest,
]
.into_iter()
.find(|network| chain_hash_from_network(*network) == chain_hash)
}
pub(crate) fn random_serial_id() -> u64 {
thread_rng().gen()
}
pub(crate) fn random_temporary_contract_id() -> [u8; 32] {
let mut id = [0u8; 32];
thread_rng().fill(&mut id);
id
}