use bitcoin::Transaction;
use ddk_dlc::secp256k1_zkp::{
ecdsa::Signature, All, EcdsaAdaptorSignature, PublicKey, Secp256k1, SecretKey,
};
use ddk_messages::oracle_msgs::OracleAttestation;
use ddk_messages::{AcceptDlc, OfferDlc, SignDlc};
use super::context::{context_from_messages, ensure_sign_message};
use super::error::ContractError;
use super::types::Party;
pub fn sign_cet(
offer: &OfferDlc,
accept: &AcceptDlc,
sign: &SignDlc,
funding_secret_key: &SecretKey,
attestations: &[(usize, OracleAttestation)],
) -> Result<Transaction, ContractError> {
let secp = Secp256k1::new();
let context = context_from_messages(offer, accept)?;
ensure_sign_message(offer, sign, &context)?;
let party = settling_party(&secp, offer, accept, funding_secret_key)?;
let (counterparty_pubkey, adaptor_signatures) =
counterparty_adaptor_signatures(offer, accept, sign, party);
let total_collateral = offer.get_total_collateral();
let funding_script_pubkey = &context.transactions.funding_script_pubkey;
let fund_value = context.transactions.get_fund_output().value;
let outcomes: Vec<(usize, &Vec<String>)> = attestations
.iter()
.map(|(index, attestation)| (*index, &attestation.outcomes))
.collect();
let mut signature_index = 0;
for (info, cet_range) in context.execution_infos.iter().zip(&context.cet_ranges) {
let (adaptor_info, next_index) = info
.verify_and_get_adaptor_info(
&secp,
total_collateral,
&counterparty_pubkey,
funding_script_pubkey,
fund_value,
&context.transactions.cets[cet_range.clone()],
&adaptor_signatures,
signature_index,
)
.map_err(|e| {
counterparty_error(party)(format!("invalid CET adaptor signatures: {e}"))
})?;
let Some((signature_infos, range_info)) =
info.get_range_info_for_outcome(&adaptor_info, &outcomes, signature_index)
else {
signature_index = next_index;
continue;
};
validate_attestations(&secp, &info.oracle_announcements, attestations)?;
let mut cet = context.transactions.cets[cet_range.start + range_info.cet_index].clone();
let oracle_signatures: Vec<Vec<_>> = attestations
.iter()
.filter_map(|(index, attestation)| {
let signature_info = signature_infos.iter().find(|info| info.0 == *index)?;
Some(
attestation
.signatures
.iter()
.take(signature_info.1)
.cloned()
.collect(),
)
})
.collect();
ddk_dlc::sign_cet(
&secp,
&mut cet,
&adaptor_signatures[range_info.adaptor_index],
&oracle_signatures,
funding_secret_key,
&counterparty_pubkey,
funding_script_pubkey,
fund_value,
)?;
return Ok(cet);
}
Err(ContractError::NoMatchingOutcome)
}
pub fn sign_refund(
offer: &OfferDlc,
accept: &AcceptDlc,
sign: &SignDlc,
funding_secret_key: &SecretKey,
) -> Result<Transaction, ContractError> {
let secp = Secp256k1::new();
let context = context_from_messages(offer, accept)?;
ensure_sign_message(offer, sign, &context)?;
let party = settling_party(&secp, offer, accept, funding_secret_key)?;
let (counterparty_pubkey, counterparty_signature): (PublicKey, Signature) = match party {
Party::Offer => (accept.funding_pubkey, accept.refund_signature),
Party::Accept => (offer.funding_pubkey, sign.refund_signature),
};
let funding_script_pubkey = &context.transactions.funding_script_pubkey;
let fund_value = context.transactions.get_fund_output().value;
ddk_dlc::verify_tx_input_sig(
&secp,
&counterparty_signature,
&context.transactions.refund,
0,
funding_script_pubkey,
fund_value,
&counterparty_pubkey,
)
.map_err(|e| counterparty_error(party)(format!("invalid refund signature: {e}")))?;
let mut refund = context.transactions.refund.clone();
ddk_dlc::util::sign_multi_sig_input(
&secp,
&mut refund,
&counterparty_signature,
&counterparty_pubkey,
funding_secret_key,
funding_script_pubkey,
fund_value,
0,
)?;
Ok(refund)
}
fn settling_party(
secp: &Secp256k1<All>,
offer: &OfferDlc,
accept: &AcceptDlc,
funding_secret_key: &SecretKey,
) -> Result<Party, ContractError> {
let public_key = PublicKey::from_secret_key(secp, funding_secret_key);
if public_key == offer.funding_pubkey {
Ok(Party::Offer)
} else if public_key == accept.funding_pubkey {
Ok(Party::Accept)
} else {
Err(ContractError::Key(
"funding secret key does not match either party's funding public key".to_string(),
))
}
}
fn counterparty_adaptor_signatures(
offer: &OfferDlc,
accept: &AcceptDlc,
sign: &SignDlc,
party: Party,
) -> (PublicKey, Vec<EcdsaAdaptorSignature>) {
match party {
Party::Offer => (
accept.funding_pubkey,
(&accept.cet_adaptor_signatures).into(),
),
Party::Accept => (offer.funding_pubkey, (&sign.cet_adaptor_signatures).into()),
}
}
fn counterparty_error(party: Party) -> fn(String) -> ContractError {
match party {
Party::Offer => ContractError::InvalidAccept,
Party::Accept => ContractError::InvalidSign,
}
}
fn validate_attestations(
secp: &Secp256k1<All>,
announcements: &[ddk_messages::oracle_msgs::OracleAnnouncement],
attestations: &[(usize, OracleAttestation)],
) -> Result<(), ContractError> {
for (index, attestation) in attestations {
let announcement = announcements.get(*index).ok_or_else(|| {
ContractError::InvalidAttestation(format!(
"attestation refers to oracle {index} but the contract has {} oracles",
announcements.len()
))
})?;
attestation.validate(secp, announcement).map_err(|e| {
ContractError::InvalidAttestation(format!("attestation from oracle {index}: {e}"))
})?;
}
Ok(())
}