use std::ops::Deref;
use bitcoin::hex::DisplayHex;
use bitcoin::psbt::Psbt;
use bitcoin::Amount;
use bitcoin::{consensus::Decodable, Script, Transaction, Witness};
use ddk_dlc::dlc_input::DlcInputInfo;
use ddk_dlc::{DlcTransactions, PartyParams};
use ddk_messages::{
oracle_msgs::{OracleAnnouncement, OracleAttestation},
AcceptDlc, FundingSignature, FundingSignatures, OfferDlc, SignDlc, WitnessElement,
};
use ddk_messages::{CloseDlc, FundingInput};
use lightning::util::logger::Logger;
use lightning::{log_debug, log_info};
use secp256k1_zkp::{
ecdsa::Signature, All, EcdsaAdaptorSignature, PublicKey, Secp256k1, SecretKey, Signing,
};
use crate::dlc_input::{get_dlc_inputs_from_funding_inputs, get_signature_for_dlc_input};
use crate::Storage;
use crate::{
contract::{
accepted_contract::AcceptedContract, contract_info::ContractInfo,
contract_input::ContractInput, offered_contract::OfferedContract,
signed_contract::SignedContract, AdaptorInfo,
},
conversion_utils::get_tx_input_infos,
error::Error,
Blockchain, ChannelId, ContractSigner, ContractSignerProvider, Time, Wallet,
};
#[allow(clippy::too_many_arguments)]
pub async fn offer_contract<
W: Deref,
B: Deref,
T: Deref,
X: ContractSigner,
SP: Deref,
C: Signing,
L: Deref,
>(
secp: &Secp256k1<C>,
contract_input: &ContractInput,
oracle_announcements: Vec<Vec<OracleAnnouncement>>,
dlc_inputs: Vec<DlcInputInfo>,
refund_delay: u32,
counter_party: &PublicKey,
wallet: &W,
blockchain: &B,
time: &T,
signer_provider: &SP,
logger: &L,
) -> Result<(OfferedContract, OfferDlc), Error>
where
W::Target: Wallet,
B::Target: Blockchain,
T::Target: Time,
SP::Target: ContractSignerProvider<Signer = X>,
L::Target: Logger,
{
contract_input.validate()?;
let id = crate::utils::get_new_temporary_id();
let keys_id = signer_provider.derive_signer_key_id(true, id);
let signer = signer_provider.derive_contract_signer(keys_id)?;
let total_collateral = contract_input.offer_collateral + contract_input.accept_collateral;
let (party_params, funding_inputs_info) = crate::utils::get_party_params(
secp,
contract_input.offer_collateral,
total_collateral,
dlc_inputs,
contract_input.fee_rate,
wallet,
&signer,
blockchain,
)
.await?;
log_debug!(
logger,
"Created offer contract with offer party params. temp_id={} counter_party={} fund_pubkey={} change_spk={} payout_spk={} dlc_inputs={} input_amount={} collateral={}",
id.to_lower_hex_string(),
counter_party.to_string(),
party_params.fund_pubkey.to_string(),
party_params.change_script_pubkey.to_string(),
party_params.payout_script_pubkey.to_string(),
!party_params.dlc_inputs.is_empty(),
party_params.input_amount.to_sat(),
party_params.collateral.to_sat(),
);
let offered_contract = OfferedContract::new(
id,
contract_input,
oracle_announcements,
&party_params,
&funding_inputs_info,
counter_party,
refund_delay,
time.unix_time_now() as u32,
keys_id,
);
let offer_msg: OfferDlc = (&offered_contract).into();
Ok((offered_contract, offer_msg))
}
pub async fn accept_contract<W: Deref, X: ContractSigner, SP: Deref, B: Deref, L: Deref>(
secp: &Secp256k1<All>,
offered_contract: &OfferedContract,
wallet: &W,
signer_provider: &SP,
blockchain: &B,
logger: &L,
) -> Result<(AcceptedContract, AcceptDlc), Error>
where
W::Target: Wallet,
B::Target: Blockchain,
SP::Target: ContractSignerProvider<Signer = X>,
L::Target: Logger,
{
let total_collateral = offered_contract.total_collateral;
let signer = signer_provider.derive_contract_signer(offered_contract.keys_id)?;
let (accept_params, funding_inputs) = crate::utils::get_party_params(
secp,
total_collateral - offered_contract.offer_params.collateral,
total_collateral,
vec![],
offered_contract.fee_rate_per_vb,
wallet,
&signer,
blockchain,
)
.await?;
log_debug!(
logger,
"Created accept party params. temp_id={} counter_party={} fund_pubkey={} change_spk={} payout_spk={} dlc_inputs={} input_amount={} collateral={}",
offered_contract.id.to_lower_hex_string(),
offered_contract.counter_party.to_string(),
accept_params.fund_pubkey.to_string(),
accept_params.change_script_pubkey.to_string(),
accept_params.payout_script_pubkey.to_string(),
!accept_params.dlc_inputs.is_empty(),
accept_params.input_amount.to_sat(),
accept_params.collateral.to_sat(),
);
let has_dlc_inputs = !accept_params.dlc_inputs.is_empty()
|| !offered_contract.offer_params.dlc_inputs.is_empty();
let dlc_transactions = if has_dlc_inputs {
log_debug!(
logger,
"Creating spliced DLC transactions. num_dlc_inputs={}",
accept_params.dlc_inputs.len() + offered_contract.offer_params.dlc_inputs.len()
);
ddk_dlc::create_spliced_dlc_transactions(
&offered_contract.offer_params,
&accept_params,
&offered_contract.contract_info[0].get_payouts(total_collateral)?,
offered_contract.refund_locktime,
offered_contract.fee_rate_per_vb,
0,
offered_contract.cet_locktime,
offered_contract.fund_output_serial_id,
offered_contract.contract_flags,
)?
} else {
log_debug!(logger, "Creating DLC transactions without splicing.");
ddk_dlc::create_dlc_transactions(
&offered_contract.offer_params,
&accept_params,
&offered_contract.contract_info[0].get_payouts(total_collateral)?,
offered_contract.refund_locktime,
offered_contract.fee_rate_per_vb,
0,
offered_contract.cet_locktime,
offered_contract.fund_output_serial_id,
offered_contract.contract_flags,
)?
};
log_info!(
logger,
"Created DLC transactions. temp_id={} fund_txid={} funding_spk={} fund_output_value={} refund_txid={} num_cets={}",
offered_contract.id.to_lower_hex_string(),
dlc_transactions.fund.compute_txid().to_string(),
dlc_transactions.funding_script_pubkey.to_string(),
dlc_transactions.get_fund_output().value.to_sat(),
dlc_transactions.refund.compute_txid().to_string(),
dlc_transactions.cets.len()
);
let fund_output_value = dlc_transactions.get_fund_output().value;
let (accepted_contract, adaptor_sigs) = accept_contract_internal(
secp,
offered_contract,
&accept_params,
&funding_inputs,
&signer.get_secret_key()?,
fund_output_value,
None,
&dlc_transactions,
)?;
log_info!(
logger,
"Created accept contract. temp_id={} contract_id={}",
offered_contract.id.to_lower_hex_string(),
accepted_contract.get_contract_id().to_lower_hex_string()
);
let accept_msg: AcceptDlc = accepted_contract.get_accept_contract_msg(&adaptor_sigs);
Ok((accepted_contract, accept_msg))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn accept_contract_internal(
secp: &Secp256k1<All>,
offered_contract: &OfferedContract,
accept_params: &PartyParams,
funding_inputs: &[FundingInput],
adaptor_secret_key: &SecretKey,
input_value: Amount,
input_script_pubkey: Option<&Script>,
dlc_transactions: &DlcTransactions,
) -> Result<(AcceptedContract, Vec<EcdsaAdaptorSignature>), crate::Error> {
let total_collateral = offered_contract.total_collateral;
let input_script_pubkey =
input_script_pubkey.unwrap_or_else(|| &dlc_transactions.funding_script_pubkey);
let cet_input = dlc_transactions.cets[0].input[0].clone();
let (adaptor_info, adaptor_sig) = offered_contract.contract_info[0].get_adaptor_info(
secp,
offered_contract.total_collateral,
adaptor_secret_key,
input_script_pubkey,
input_value,
&dlc_transactions.cets,
0,
)?;
let mut adaptor_infos = vec![adaptor_info];
let mut adaptor_sigs = adaptor_sig;
let DlcTransactions {
fund,
cets,
refund,
funding_script_pubkey,
pending_close_txs: _,
} = dlc_transactions;
let mut cets = cets.clone();
for contract_info in offered_contract.contract_info.iter().skip(1) {
let payouts = contract_info.get_payouts(total_collateral)?;
let tmp_cets = ddk_dlc::create_cets(
&cet_input,
&offered_contract.offer_params.payout_script_pubkey,
offered_contract.offer_params.payout_serial_id,
&accept_params.payout_script_pubkey,
accept_params.payout_serial_id,
&payouts,
0,
);
let (adaptor_info, adaptor_sig) = contract_info.get_adaptor_info(
secp,
offered_contract.total_collateral,
adaptor_secret_key,
input_script_pubkey,
input_value,
&tmp_cets,
adaptor_sigs.len(),
)?;
cets.extend(tmp_cets);
adaptor_infos.push(adaptor_info);
adaptor_sigs.extend(adaptor_sig);
}
let refund_signature = ddk_dlc::util::get_raw_sig_for_tx_input(
secp,
refund,
0,
input_script_pubkey,
input_value,
adaptor_secret_key,
)?;
let dlc_transactions = DlcTransactions {
fund: fund.clone(),
cets,
refund: refund.clone(),
funding_script_pubkey: funding_script_pubkey.clone(),
pending_close_txs: vec![],
};
let accepted_contract = AcceptedContract {
offered_contract: offered_contract.clone(),
adaptor_infos,
adaptor_signatures: adaptor_sigs.clone(),
accept_params: accept_params.clone(),
funding_inputs: funding_inputs.to_vec(),
dlc_transactions,
accept_refund_signature: refund_signature,
};
Ok((accepted_contract, adaptor_sigs))
}
pub async fn verify_accepted_and_sign_contract<
W: Deref,
X: ContractSigner,
SP: Deref,
S: Deref,
L: Deref,
>(
secp: &Secp256k1<All>,
offered_contract: &OfferedContract,
accept_msg: &AcceptDlc,
wallet: &W,
signer_provider: &SP,
storage: &S,
logger: &L,
) -> Result<(SignedContract, SignDlc), Error>
where
W::Target: Wallet,
SP::Target: ContractSignerProvider<Signer = X>,
S::Target: Storage,
L::Target: Logger,
{
let (tx_input_infos, input_amount) = get_tx_input_infos(&accept_msg.funding_inputs)?;
let accept_dlc_inputs = get_dlc_inputs_from_funding_inputs(&accept_msg.funding_inputs);
let accept_params = PartyParams {
fund_pubkey: accept_msg.funding_pubkey,
change_script_pubkey: accept_msg.change_spk.clone(),
change_serial_id: accept_msg.change_serial_id,
payout_script_pubkey: accept_msg.payout_spk.clone(),
payout_serial_id: accept_msg.payout_serial_id,
inputs: tx_input_infos,
dlc_inputs: accept_dlc_inputs.clone(),
input_amount,
collateral: accept_msg.accept_collateral,
};
log_debug!(
logger,
"Retrieved the party params for the accepting party. temp_id={} counter_party={} fund_pubkey={} change_spk={} payout_spk={} dlc_inputs={} input_amount={} collateral={}",
offered_contract.id.to_lower_hex_string(),
offered_contract.counter_party.to_string(),
accept_params.fund_pubkey.to_string(),
accept_params.change_script_pubkey.to_string(),
accept_params.payout_script_pubkey.to_string(),
!accept_params.dlc_inputs.is_empty(),
accept_params.input_amount.to_sat(),
accept_params.collateral.to_sat(),
);
let cet_adaptor_signatures = accept_msg
.cet_adaptor_signatures
.ecdsa_adaptor_signatures
.iter()
.map(|x| x.signature)
.collect::<Vec<_>>();
let total_collateral = offered_contract.total_collateral;
let has_dlc_inputs =
!accept_dlc_inputs.is_empty() || !offered_contract.offer_params.dlc_inputs.is_empty();
let dlc_transactions = if has_dlc_inputs {
log_debug!(
logger,
"Creating spliced DLC transactions. num_dlc_inputs={}",
accept_dlc_inputs.len() + offered_contract.offer_params.dlc_inputs.len()
);
ddk_dlc::create_spliced_dlc_transactions(
&offered_contract.offer_params,
&accept_params,
&offered_contract.contract_info[0].get_payouts(total_collateral)?,
offered_contract.refund_locktime,
offered_contract.fee_rate_per_vb,
0,
offered_contract.cet_locktime,
offered_contract.fund_output_serial_id,
offered_contract.contract_flags,
)?
} else {
log_debug!(logger, "Creating DLC transactions without splicing.");
ddk_dlc::create_dlc_transactions(
&offered_contract.offer_params,
&accept_params,
&offered_contract.contract_info[0].get_payouts(total_collateral)?,
offered_contract.refund_locktime,
offered_contract.fee_rate_per_vb,
0,
offered_contract.cet_locktime,
offered_contract.fund_output_serial_id,
offered_contract.contract_flags,
)?
};
log_info!(
logger,
"Created DLC transactions. temp_id={} fund_txid={} funding_spk={} fund_output_value={} refund_txid={} num_cets={}",
offered_contract.id.to_lower_hex_string(),
dlc_transactions.fund.compute_txid().to_string(),
dlc_transactions.funding_script_pubkey.to_string(),
dlc_transactions.get_fund_output().value.to_sat(),
dlc_transactions.refund.compute_txid().to_string(),
dlc_transactions.cets.len()
);
let fund_output_value = dlc_transactions.get_fund_output().value;
let signer = signer_provider.derive_contract_signer(offered_contract.keys_id)?;
let (signed_contract, adaptor_sigs) = verify_accepted_and_sign_contract_internal(
secp,
offered_contract,
&accept_params,
&accept_msg.funding_inputs,
&accept_msg.refund_signature,
&cet_adaptor_signatures,
fund_output_value,
wallet,
&signer,
None,
None,
&dlc_transactions,
None,
storage,
signer_provider,
logger,
)
.await?;
let contract_id = signed_contract.accepted_contract.get_contract_id_string();
log_info!(
logger,
"Signed and verified accept message. tmp_id={} contract_id={} num_adaptor_sigs={}",
offered_contract.id.to_lower_hex_string(),
contract_id,
adaptor_sigs.len()
);
let signed_msg: SignDlc = signed_contract.get_sign_dlc(adaptor_sigs);
Ok((signed_contract, signed_msg))
}
fn populate_psbt(psbt: &mut Psbt, all_funding_inputs: &[&FundingInput]) -> Result<(), Error> {
for (input_index, x) in all_funding_inputs.iter().enumerate() {
let tx = Transaction::consensus_decode(&mut x.prev_tx.as_slice()).map_err(|_| {
Error::InvalidParameters(
"Could not decode funding input previous tx parameter".to_string(),
)
})?;
let vout = x.prev_tx_vout;
let tx_out = tx.output.get(vout as usize).ok_or_else(|| {
Error::InvalidParameters(format!("Previous tx output not found at index {vout}"))
})?;
psbt.inputs[input_index].witness_utxo = Some(tx_out.clone());
psbt.inputs[input_index].redeem_script = Some(x.redeem_script.clone());
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn verify_accepted_and_sign_contract_internal<
W: Deref,
X: ContractSigner,
S: Deref,
SP: Deref,
L: Deref,
>(
secp: &Secp256k1<All>,
offered_contract: &OfferedContract,
accept_params: &PartyParams,
funding_inputs_info: &[FundingInput],
refund_signature: &Signature,
cet_adaptor_signatures: &[EcdsaAdaptorSignature],
input_value: Amount,
wallet: &W,
signer: &X,
input_script_pubkey: Option<&Script>,
counter_adaptor_pk: Option<PublicKey>,
dlc_transactions: &DlcTransactions,
channel_id: Option<ChannelId>,
storage: &S,
signer_provider: &SP,
logger: &L,
) -> Result<(SignedContract, Vec<EcdsaAdaptorSignature>), Error>
where
W::Target: Wallet,
S::Target: Storage,
SP::Target: ContractSignerProvider<Signer = X>,
L::Target: Logger,
{
let DlcTransactions {
fund,
cets,
refund,
funding_script_pubkey,
pending_close_txs: _,
} = dlc_transactions;
let mut fund_psbt = Psbt::from_unsigned_tx(fund.clone())
.map_err(|_| Error::InvalidState("Tried to create PSBT from signed tx".to_string()))?;
let mut cets = cets.clone();
let input_script_pubkey = input_script_pubkey.unwrap_or_else(|| funding_script_pubkey);
let counter_adaptor_pk = counter_adaptor_pk.unwrap_or(accept_params.fund_pubkey);
ddk_dlc::verify_tx_input_sig(
secp,
refund_signature,
refund,
0,
input_script_pubkey,
input_value,
&counter_adaptor_pk,
)?;
log_debug!(
logger,
"Verified refund signature. temp_id={} refund_txid={}",
offered_contract.id.to_lower_hex_string(),
refund.compute_txid().to_string(),
);
let (adaptor_info, mut adaptor_index) = offered_contract.contract_info[0]
.verify_and_get_adaptor_info(
secp,
offered_contract.total_collateral,
&counter_adaptor_pk,
input_script_pubkey,
input_value,
&cets,
cet_adaptor_signatures,
0,
)?;
let mut adaptor_infos = vec![adaptor_info];
let cet_input = cets[0].input[0].clone();
let total_collateral = offered_contract.offer_params.collateral + accept_params.collateral;
for contract_info in offered_contract.contract_info.iter().skip(1) {
let payouts = contract_info.get_payouts(total_collateral)?;
let tmp_cets = ddk_dlc::create_cets(
&cet_input,
&offered_contract.offer_params.payout_script_pubkey,
offered_contract.offer_params.payout_serial_id,
&accept_params.payout_script_pubkey,
accept_params.payout_serial_id,
&payouts,
0,
);
let (adaptor_info, tmp_adaptor_index) = contract_info.verify_and_get_adaptor_info(
secp,
offered_contract.total_collateral,
&accept_params.fund_pubkey,
funding_script_pubkey,
input_value,
&tmp_cets,
cet_adaptor_signatures,
adaptor_index,
)?;
adaptor_index = tmp_adaptor_index;
cets.extend(tmp_cets);
adaptor_infos.push(adaptor_info);
}
let mut own_signatures: Vec<EcdsaAdaptorSignature> = Vec::new();
for (contract_info, adaptor_info) in offered_contract
.contract_info
.iter()
.zip(adaptor_infos.iter())
{
let sigs = contract_info.get_adaptor_signatures(
secp,
adaptor_info,
&signer,
input_script_pubkey,
input_value,
&cets,
)?;
own_signatures.extend(sigs);
}
let mut all_funding_inputs = offered_contract
.funding_inputs
.iter()
.chain(funding_inputs_info.iter())
.collect::<Vec<_>>();
all_funding_inputs.sort_by_key(|x| x.input_serial_id);
log_info!(
logger,
"Populating PSBT for signing funding inputs. num_funding_inputs={}",
all_funding_inputs.len(),
);
populate_psbt(&mut fund_psbt, &all_funding_inputs)?;
let mut witnesses: Vec<Witness> = Vec::new();
for funding_input in offered_contract.funding_inputs.iter() {
let input_index = all_funding_inputs
.iter()
.position(|y| y == &funding_input)
.ok_or_else(|| {
Error::InvalidState(format!(
"Could not find input for serial id {}",
funding_input.input_serial_id
))
})?;
if let Some(dlc_input) = &funding_input.dlc_input {
log_debug!(
logger,
"Signing DLC input. temp_id={} splice_contract_id={} local_fund_pubkey={} remote_fund_pubkey={} input_index={}",
offered_contract.id.to_lower_hex_string(),
dlc_input.contract_id.to_lower_hex_string(),
dlc_input.local_fund_pubkey.to_string(),
dlc_input.remote_fund_pubkey.to_string(),
input_index,
);
let dlc_input_signature = get_signature_for_dlc_input(
secp,
funding_input,
fund,
input_index,
&dlc_input.contract_id,
storage,
signer_provider,
)
.await?;
let witness = Witness::from_slice(&[dlc_input_signature]);
witnesses.push(witness);
continue;
}
wallet.sign_psbt_input(&mut fund_psbt, input_index).await?;
let witness = fund_psbt.inputs[input_index]
.final_script_witness
.clone()
.ok_or(Error::InvalidParameters(
"No witness from signing psbt input".to_string(),
))?;
witnesses.push(witness);
}
log_debug!(
logger,
"Signed funding inputs. temp_id={} num_signatures={}",
offered_contract.id.to_lower_hex_string(),
witnesses.len()
);
let funding_signatures: Vec<FundingSignature> = witnesses
.into_iter()
.map(|witness| {
let witness_elements = witness
.iter()
.map(|z| WitnessElement {
witness: z.to_vec(),
})
.collect();
Ok(FundingSignature { witness_elements })
})
.collect::<Result<Vec<_>, Error>>()?;
let offer_refund_signature = ddk_dlc::util::get_raw_sig_for_tx_input(
secp,
refund,
0,
input_script_pubkey,
input_value,
&signer.get_secret_key()?,
)?;
let dlc_transactions = DlcTransactions {
fund: fund.clone(),
cets,
refund: refund.clone(),
funding_script_pubkey: funding_script_pubkey.clone(),
pending_close_txs: vec![],
};
let accepted_contract = AcceptedContract {
offered_contract: offered_contract.clone(),
accept_params: accept_params.clone(),
funding_inputs: funding_inputs_info.to_vec(),
adaptor_infos,
adaptor_signatures: cet_adaptor_signatures.to_vec(),
accept_refund_signature: *refund_signature,
dlc_transactions,
};
let signed_contract = SignedContract {
accepted_contract,
adaptor_signatures: own_signatures.clone(),
offer_refund_signature,
funding_signatures: FundingSignatures { funding_signatures },
channel_id,
};
Ok((signed_contract, own_signatures))
}
pub async fn verify_signed_contract<W: Deref, S: Deref, SP: Deref, X: ContractSigner, L: Deref>(
secp: &Secp256k1<All>,
accepted_contract: &AcceptedContract,
sign_msg: &SignDlc,
wallet: &W,
storage: &S,
signer_provider: &SP,
logger: &L,
) -> Result<(SignedContract, Transaction), Error>
where
W::Target: Wallet,
S::Target: Storage,
SP::Target: ContractSignerProvider<Signer = X>,
L::Target: Logger,
{
let cet_adaptor_signatures: Vec<_> = (&sign_msg.cet_adaptor_signatures).into();
verify_signed_contract_internal(
secp,
accepted_contract,
&sign_msg.refund_signature,
&cet_adaptor_signatures,
&sign_msg.funding_signatures,
accepted_contract.dlc_transactions.get_fund_output().value,
None,
None,
wallet,
None,
storage,
signer_provider,
logger,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn verify_signed_contract_internal<
W: Deref,
SP: Deref,
S: Deref,
X: ContractSigner,
L: Deref,
>(
secp: &Secp256k1<All>,
accepted_contract: &AcceptedContract,
refund_signature: &Signature,
cet_adaptor_signatures: &[EcdsaAdaptorSignature],
funding_signatures: &FundingSignatures,
input_value: Amount,
input_script_pubkey: Option<&Script>,
counter_adaptor_pk: Option<PublicKey>,
wallet: &W,
channel_id: Option<ChannelId>,
storage: &S,
signer_provider: &SP,
logger: &L,
) -> Result<(SignedContract, Transaction), Error>
where
W::Target: Wallet,
S::Target: Storage,
SP::Target: ContractSignerProvider<Signer = X>,
L::Target: Logger,
{
let offered_contract = &accepted_contract.offered_contract;
let input_script_pubkey = input_script_pubkey
.unwrap_or_else(|| &accepted_contract.dlc_transactions.funding_script_pubkey);
let counter_adaptor_pk =
counter_adaptor_pk.unwrap_or(accepted_contract.offered_contract.offer_params.fund_pubkey);
ddk_dlc::verify_tx_input_sig(
secp,
refund_signature,
&accepted_contract.dlc_transactions.refund,
0,
input_script_pubkey,
input_value,
&counter_adaptor_pk,
)?;
log_debug!(
logger,
"Verified refund signature. contract_id={} refund_txid={}",
accepted_contract.get_contract_id_string(),
accepted_contract
.dlc_transactions
.refund
.compute_txid()
.to_string(),
);
let mut adaptor_sig_start = 0;
for (adaptor_info, contract_info) in accepted_contract
.adaptor_infos
.iter()
.zip(offered_contract.contract_info.iter())
{
adaptor_sig_start = contract_info.verify_adaptor_info(
secp,
&counter_adaptor_pk,
input_script_pubkey,
input_value,
&accepted_contract.dlc_transactions.cets,
cet_adaptor_signatures,
adaptor_sig_start,
adaptor_info,
)?;
}
log_debug!(
logger,
"Verified adaptor signatures. contract_id={} num_adaptor_infos={}",
accepted_contract.get_contract_id_string(),
accepted_contract.adaptor_infos.len(),
);
let fund_tx = &accepted_contract.dlc_transactions.fund;
let mut fund_psbt = Psbt::from_unsigned_tx(fund_tx.clone())
.map_err(|_| Error::InvalidState("Tried to create PSBT from signed tx".to_string()))?;
let mut all_funding_inputs = offered_contract
.funding_inputs
.iter()
.chain(accepted_contract.funding_inputs.iter())
.collect::<Vec<_>>();
all_funding_inputs.sort_by_key(|x| x.input_serial_id);
log_debug!(
logger,
"Populating PSBT for signing funding inputs. contract_id={} num_funding_inputs={}",
accepted_contract.get_contract_id_string(),
all_funding_inputs.len(),
);
populate_psbt(&mut fund_psbt, &all_funding_inputs)?;
for (funding_input, funding_signatures) in offered_contract
.funding_inputs
.iter()
.zip(funding_signatures.funding_signatures.iter())
{
let input_index = all_funding_inputs
.iter()
.position(|x| x == &funding_input)
.ok_or_else(|| {
Error::InvalidState(format!(
"Could not find input for serial id {}",
funding_input.input_serial_id
))
})?;
if let Some(dlc_input) = &funding_input.dlc_input {
let dlc_input_info: DlcInputInfo = funding_input.into();
log_debug!(
logger,
"Verifying DLC input signature. contract_id={} input_index={} remote_fund_pubkey={} local_fund_pubkey={}",
accepted_contract.get_contract_id_string(),
input_index,
dlc_input.remote_fund_pubkey.to_string(),
dlc_input.local_fund_pubkey.to_string(),
);
ddk_dlc::dlc_input::verify_dlc_funding_input_signature(
secp,
fund_tx,
input_index,
&dlc_input_info,
funding_signatures.witness_elements[0].witness.clone(),
&dlc_input.local_fund_pubkey,
)?;
log_debug!(
logger,
"Signing DLC input. contract_id={} input_index={}",
accepted_contract.get_contract_id_string(),
input_index,
);
let my_dlc_input_signature = get_signature_for_dlc_input(
secp,
funding_input,
fund_tx,
input_index,
&dlc_input.contract_id,
storage,
signer_provider,
)
.await?;
let completed_witness = ddk_dlc::dlc_input::combine_dlc_input_signatures(
&dlc_input_info,
&my_dlc_input_signature,
&funding_signatures.witness_elements[0].witness,
&dlc_input.remote_fund_pubkey,
&dlc_input.local_fund_pubkey,
);
log_debug!(
logger,
"Completed the signatures for the DLC input. contract_id={} input_index={}",
accepted_contract.get_contract_id_string(),
input_index,
);
fund_psbt.inputs[input_index].final_script_witness = Some(completed_witness);
} else {
fund_psbt.inputs[input_index].final_script_witness = Some(Witness::from_slice(
&funding_signatures
.witness_elements
.iter()
.map(|x| x.witness.clone())
.collect::<Vec<_>>(),
));
}
}
for funding_input in &accepted_contract.funding_inputs {
let input_index = all_funding_inputs
.iter()
.position(|x| x == &funding_input)
.ok_or_else(|| {
Error::InvalidState(format!(
"Could not find input for serial id {}",
funding_input.input_serial_id
))
})?;
log_debug!(
logger,
"Signing funding input. contract_id={} input_index={}",
accepted_contract.get_contract_id_string(),
input_index,
);
wallet.sign_psbt_input(&mut fund_psbt, input_index).await?;
}
let signed_contract = SignedContract {
accepted_contract: accepted_contract.clone(),
adaptor_signatures: cet_adaptor_signatures.to_vec(),
offer_refund_signature: *refund_signature,
funding_signatures: funding_signatures.clone(),
channel_id,
};
let transaction = fund_psbt.extract_tx_unchecked_fee_rate();
Ok((signed_contract, transaction))
}
pub fn get_signed_cet<C: Signing, S: Deref, L: Deref>(
secp: &Secp256k1<C>,
contract: &SignedContract,
contract_info: &ContractInfo,
adaptor_info: &AdaptorInfo,
attestations: &[(usize, OracleAttestation)],
signer: S,
logger: &L,
) -> Result<Transaction, Error>
where
S::Target: ContractSigner,
L::Target: Logger,
{
let contract_id = contract.accepted_contract.get_contract_id_string();
log_info!(
logger,
"Getting the signed CET for the Oracle Attestation. contract_id={} outcomes={:?} event_id={}",
contract_id,
attestations
.iter()
.map(|(_, a)| &a.outcomes)
.collect::<Vec<_>>(),
attestations.first().unwrap().1.event_id,
);
let (range_info, sigs) =
crate::utils::get_range_info_and_oracle_sigs(contract_info, adaptor_info, attestations)?;
let mut cet = contract.accepted_contract.dlc_transactions.cets[range_info.cet_index].clone();
let offered_contract = &contract.accepted_contract.offered_contract;
let (adaptor_sigs, other_pubkey) = if offered_contract.is_offer_party {
(
contract.accepted_contract.adaptor_signatures.clone(),
&contract.accepted_contract.accept_params.fund_pubkey,
)
} else {
(
contract.adaptor_signatures.clone(),
&offered_contract.offer_params.fund_pubkey,
)
};
let funding_sk = signer.get_secret_key()?;
ddk_dlc::sign_cet(
secp,
&mut cet,
&adaptor_sigs[range_info.adaptor_index],
&sigs,
&funding_sk,
other_pubkey,
&contract
.accepted_contract
.dlc_transactions
.funding_script_pubkey,
contract
.accepted_contract
.dlc_transactions
.get_fund_output()
.value,
)?;
Ok(cet)
}
pub fn get_signed_refund<C: Signing, S: Deref, L: Deref>(
secp: &Secp256k1<C>,
contract: &SignedContract,
signer: S,
logger: &L,
) -> Result<Transaction, Error>
where
S::Target: ContractSigner,
L::Target: Logger,
{
log_info!(
logger,
"Getting signed refund transaction. contract_id={}",
contract.accepted_contract.get_contract_id_string()
);
let accepted_contract = &contract.accepted_contract;
let offered_contract = &accepted_contract.offered_contract;
let funding_script_pubkey = &accepted_contract.dlc_transactions.funding_script_pubkey;
let fund_output_value = accepted_contract.dlc_transactions.get_fund_output().value;
let (other_fund_pubkey, other_sig) = if offered_contract.is_offer_party {
(
&accepted_contract.accept_params.fund_pubkey,
&accepted_contract.accept_refund_signature,
)
} else {
(
&offered_contract.offer_params.fund_pubkey,
&contract.offer_refund_signature,
)
};
let fund_priv_key = signer.get_secret_key()?;
let mut refund = accepted_contract.dlc_transactions.refund.clone();
ddk_dlc::util::sign_multi_sig_input(
secp,
&mut refund,
other_sig,
other_fund_pubkey,
&fund_priv_key,
funding_script_pubkey,
fund_output_value,
0,
)?;
Ok(refund)
}
pub fn create_cooperative_close<C: Signing, SP: Deref, L: Deref>(
secp: &Secp256k1<C>,
signed_contract: &SignedContract,
counter_payout: Amount,
signer_provider: &SP,
logger: &L,
) -> Result<(CloseDlc, Transaction), Error>
where
SP::Target: ContractSignerProvider,
L::Target: Logger,
{
let accepted_contract = &signed_contract.accepted_contract;
let offered_contract = &accepted_contract.offered_contract;
let total_collateral = offered_contract.total_collateral;
if counter_payout > total_collateral {
return Err(Error::InvalidParameters(
"Counter payout is greater than total collateral".to_string(),
));
}
let offer_payout = total_collateral - counter_payout;
let fund_output_value = accepted_contract.dlc_transactions.get_fund_output().value;
let fund_outpoint = accepted_contract.dlc_transactions.get_fund_outpoint();
let close_tx = ddk_dlc::channel::create_collaborative_close_transaction(
&offered_contract.offer_params,
offer_payout,
&accepted_contract.accept_params,
counter_payout,
fund_outpoint,
fund_output_value,
&[], );
log_debug!(
logger,
"Created cooperative close transaction. contract_id={} close_txid={} offer_payout={} counter_payout={}",
accepted_contract.get_contract_id_string(),
close_tx.compute_txid().to_string(),
offer_payout,
counter_payout,
);
let signer = signer_provider.derive_contract_signer(offered_contract.keys_id)?;
let fund_private_key = signer.get_secret_key()?;
let close_signature = ddk_dlc::util::get_raw_sig_for_tx_input(
secp,
&close_tx,
0,
&accepted_contract.dlc_transactions.funding_script_pubkey,
fund_output_value,
&fund_private_key,
)?;
let close_message = CloseDlc {
protocol_version: crate::conversion_utils::PROTOCOL_VERSION,
contract_id: accepted_contract.get_contract_id(),
close_signature,
accept_payout: counter_payout,
fee_rate_per_vb: offered_contract.fee_rate_per_vb,
fund_input_serial_id: offered_contract.fund_output_serial_id,
funding_inputs: accepted_contract.funding_inputs.clone(),
funding_signatures: signed_contract.funding_signatures.clone(),
};
Ok((close_message, close_tx))
}
pub fn complete_cooperative_close<C: Signing, SP: Deref, L: Deref>(
secp: &Secp256k1<C>,
signed_contract: &SignedContract,
close_message: &CloseDlc,
signer_provider: &SP,
logger: &L,
) -> Result<Transaction, Error>
where
SP::Target: ContractSignerProvider,
L::Target: Logger,
{
let accepted_contract = &signed_contract.accepted_contract;
let offered_contract = &accepted_contract.offered_contract;
let fund_output_value = accepted_contract.dlc_transactions.get_fund_output().value;
let fund_outpoint = accepted_contract.dlc_transactions.get_fund_outpoint();
let total_collateral = offered_contract.total_collateral;
let offer_payout = total_collateral - close_message.accept_payout;
let mut close_tx = ddk_dlc::channel::create_collaborative_close_transaction(
&offered_contract.offer_params,
offer_payout,
&accepted_contract.accept_params,
close_message.accept_payout,
fund_outpoint,
fund_output_value,
&[], );
log_debug!(
logger,
"Recreated close transaction for cooperative close verification. contract_id={} close_txid={} offer_payout={} counter_payout={}",
accepted_contract.get_contract_id_string(),
close_tx.compute_txid().to_string(),
offer_payout,
close_message.accept_payout,
);
let signer = signer_provider.derive_contract_signer(offered_contract.keys_id)?;
let fund_private_key = signer.get_secret_key()?;
let counter_pubkey = if offered_contract.is_offer_party {
&accepted_contract.accept_params.fund_pubkey
} else {
&offered_contract.offer_params.fund_pubkey
};
ddk_dlc::util::sign_multi_sig_input(
secp,
&mut close_tx,
&close_message.close_signature,
counter_pubkey,
&fund_private_key,
&accepted_contract.dlc_transactions.funding_script_pubkey,
fund_output_value,
0,
)?;
Ok(close_tx)
}