use std::ops::Deref;
use bitcoin::{consensus::Encodable, Txid};
use dlc::{PartyParams, TxInputInfo};
use dlc_messages::{
oracle_msgs::{OracleAnnouncement, OracleAttestation},
FundingInput,
};
use dlc_trie::RangeInfo;
#[cfg(not(feature = "fuzztarget"))]
use secp256k1_zkp::rand::{thread_rng, Rng, RngCore};
use secp256k1_zkp::{PublicKey, Secp256k1, SecretKey, Signing};
use crate::{
channel::party_points::PartyBasePoints,
contract::{contract_info::ContractInfo, AdaptorInfo, FundingInputInfo},
error::Error,
Blockchain, Wallet,
};
const APPROXIMATE_CET_VBYTES: u64 = 190;
const APPROXIMATE_CLOSING_VBYTES: u64 = 168;
pub fn get_common_fee(fee_rate: u64) -> u64 {
(APPROXIMATE_CET_VBYTES + APPROXIMATE_CLOSING_VBYTES) * fee_rate
}
#[cfg(not(feature = "fuzztarget"))]
pub(crate) fn get_new_serial_id() -> u64 {
thread_rng().next_u64()
}
#[cfg(feature = "fuzztarget")]
pub(crate) fn get_new_serial_id() -> u64 {
use rand_chacha::rand_core::RngCore;
use rand_chacha::rand_core::SeedableRng;
rand_chacha::ChaCha8Rng::from_seed([0u8; 32]).next_u64()
}
#[cfg(not(feature = "fuzztarget"))]
pub(crate) fn get_new_temporary_id() -> [u8; 32] {
thread_rng().gen::<[u8; 32]>()
}
#[cfg(feature = "fuzztarget")]
pub(crate) fn get_new_temporary_id() -> [u8; 32] {
use rand_chacha::rand_core::RngCore;
use rand_chacha::rand_core::SeedableRng;
let mut res = [0u8; 32];
rand_chacha::ChaCha8Rng::from_seed([0u8; 32]).fill_bytes(&mut res);
res
}
pub(crate) fn compute_id(
fund_tx_id: Txid,
fund_output_index: u16,
temporary_id: &[u8; 32],
) -> [u8; 32] {
let mut res = [0; 32];
for i in 0..32 {
res[i] = fund_tx_id[31 - i] ^ temporary_id[i];
}
res[30] ^= ((fund_output_index >> 8) & 0xff) as u8;
res[31] ^= (fund_output_index & 0xff) as u8;
res
}
pub(crate) fn get_party_params<C: Signing, W: Deref, B: Deref>(
secp: &Secp256k1<C>,
own_collateral: u64,
fee_rate: u64,
wallet: &W,
blockchain: &B,
) -> Result<(PartyParams, SecretKey, Vec<FundingInputInfo>), Error>
where
W::Target: Wallet,
B::Target: Blockchain,
{
let funding_privkey = wallet.get_new_secret_key()?;
let funding_pubkey = PublicKey::from_secret_key(secp, &funding_privkey);
let payout_addr = wallet.get_new_address()?;
let payout_spk = payout_addr.script_pubkey();
let payout_serial_id = get_new_serial_id();
let change_addr = wallet.get_new_address()?;
let change_spk = change_addr.script_pubkey();
let change_serial_id = get_new_serial_id();
let appr_required_amount = own_collateral + get_half_common_fee(fee_rate);
let utxos = wallet.get_utxos_for_amount(appr_required_amount, Some(fee_rate), true)?;
let mut funding_inputs_info: Vec<FundingInputInfo> = Vec::new();
let mut funding_tx_info: Vec<TxInputInfo> = Vec::new();
let mut total_input = 0;
for utxo in utxos {
let prev_tx = blockchain.get_transaction(&utxo.outpoint.txid)?;
let mut writer = Vec::new();
prev_tx.consensus_encode(&mut writer)?;
let prev_tx_vout = utxo.outpoint.vout;
let sequence = 0xffffffff;
let max_witness_len = 107;
let funding_input = FundingInput {
input_serial_id: get_new_serial_id(),
prev_tx: writer,
prev_tx_vout,
sequence,
max_witness_len,
redeem_script: utxo.redeem_script,
};
total_input += prev_tx.output[prev_tx_vout as usize].value;
funding_tx_info.push((&funding_input).into());
let funding_input_info = FundingInputInfo {
funding_input,
address: Some(utxo.address.clone()),
};
funding_inputs_info.push(funding_input_info);
}
let party_params = PartyParams {
fund_pubkey: funding_pubkey,
change_script_pubkey: change_spk,
change_serial_id,
payout_script_pubkey: payout_spk,
payout_serial_id,
inputs: funding_tx_info,
collateral: own_collateral,
input_amount: total_input,
};
Ok((party_params, funding_privkey, funding_inputs_info))
}
pub(crate) fn get_party_base_points<C: Signing, W: Deref>(
secp: &Secp256k1<C>,
wallet: &W,
) -> Result<PartyBasePoints, Error>
where
W::Target: Wallet,
{
Ok(PartyBasePoints {
own_basepoint: PublicKey::from_secret_key(secp, &wallet.get_new_secret_key()?),
publish_basepoint: PublicKey::from_secret_key(secp, &wallet.get_new_secret_key()?),
revocation_basepoint: PublicKey::from_secret_key(secp, &wallet.get_new_secret_key()?),
})
}
fn get_half_common_fee(fee_rate: u64) -> u64 {
let common_fee = get_common_fee(fee_rate);
(common_fee as f64 / 2_f64).ceil() as u64
}
pub(crate) fn get_range_info_and_oracle_sigs(
contract_info: &ContractInfo,
adaptor_info: &AdaptorInfo,
attestations: &[(usize, OracleAttestation)],
) -> Result<(RangeInfo, Vec<Vec<secp256k1_zkp::schnorr::Signature>>), Error> {
let outcomes = attestations
.iter()
.map(|(i, x)| (*i, &x.outcomes))
.collect::<Vec<(usize, &Vec<String>)>>();
let info_opt = contract_info.get_range_info_for_outcome(adaptor_info, &outcomes, 0);
if let Some((sig_infos, range_info)) = info_opt {
let sigs: Vec<Vec<_>> = attestations
.iter()
.filter_map(|(i, a)| {
let sig_info = sig_infos.iter().find(|x| x.0 == *i)?;
Some(a.signatures.iter().take(sig_info.1).cloned().collect())
})
.collect();
return Ok((range_info, sigs));
}
Err(Error::InvalidState(
"Could not find closing info for given outcomes".to_string(),
))
}
pub(crate) fn get_latest_maturity_date(
announcements: &[Vec<OracleAnnouncement>],
) -> Result<u32, Error> {
announcements
.iter()
.flatten()
.map(|x| x.oracle_event.event_maturity_epoch)
.max()
.ok_or_else(|| {
Error::InvalidParameters("Could not find maximum event maturity.".to_string())
})
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use dlc_messages::oracle_msgs::{EnumEventDescriptor, EventDescriptor, OracleEvent};
use secp256k1_zkp::{
rand::{thread_rng, RngCore},
schnorr::Signature,
XOnlyPublicKey,
};
use super::*;
#[test]
fn id_computation_test() {
let transaction = bitcoin_test_utils::tx_from_string("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff020000ffffffff0101000000000000000000000000");
let output_index = 1;
let temporary_id = [34u8; 32];
let expected_id = bitcoin_test_utils::str_to_hex(
"81db60dcbef10a2d0cb92cb78400a96ee6a9b6da785d0230bdabf1e18a2d6ffb",
);
let id = compute_id(transaction.txid(), output_index, &temporary_id);
assert_eq!(expected_id, id);
}
#[test]
fn get_latest_maturity_date_test() {
let mut rand = thread_rng();
let maturity_dates: Vec<Vec<u32>> = (0..20)
.map(|_| (0..20).map(|_| rand.next_u32()).collect())
.collect();
let announcements: Vec<Vec<_>> = maturity_dates
.iter()
.map(|x| x.iter().map(|y| create_announcement(*y)).collect())
.collect();
assert_eq!(
*maturity_dates.iter().flatten().max().unwrap(),
get_latest_maturity_date(&announcements).expect("to have a latest maturity date.")
);
}
fn create_announcement(maturity: u32) -> OracleAnnouncement {
let xonly_pk = XOnlyPublicKey::from_str(
"e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443",
)
.unwrap();
OracleAnnouncement {
announcement_signature: Signature::from_str("6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE5077C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8").unwrap(),
oracle_public_key: xonly_pk,
oracle_event: OracleEvent { oracle_nonces: vec![xonly_pk], event_maturity_epoch: maturity,event_descriptor: EventDescriptor::EnumEvent(EnumEventDescriptor { outcomes: vec!["1".to_string(), "2".to_string()] }), event_id: "01".to_string() },
}
}
}