diff --git a/core/src/builder/sighash.rs b/core/src/builder/sighash.rs index 88d7db81..254372a7 100644 --- a/core/src/builder/sighash.rs +++ b/core/src/builder/sighash.rs @@ -1,9 +1,12 @@ +use crate::builder::transaction::TxHandler; use crate::config::BridgeConfig; use crate::constants::NUM_INTERMEDIATE_STEPS; use crate::errors::BridgeError; -use crate::{actor::Actor, builder, database::Database, EVMAddress}; +use crate::{builder, database::Database, EVMAddress}; use async_stream::try_stream; -use bitcoin::{address::NetworkUnchecked, Address, Amount, OutPoint}; +use bitcoin::sighash::SighashCache; +use bitcoin::taproot::LeafVersion; +use bitcoin::{address::NetworkUnchecked, Address, Amount, OutPoint, TapLeafHash, TapSighashType}; use bitcoin::{TapSighash, Txid}; use futures_core::stream::Stream; @@ -15,6 +18,67 @@ pub fn calculate_num_required_sigs( num_operators * num_time_txs * (1 + 3 * num_watchtowers + 1) } +pub fn convert_tx_to_pubkey_spend( + tx_handler: &mut TxHandler, + txin_index: usize, + sighash_type: Option, +) -> Result { + let mut sighash_cache: SighashCache<&mut bitcoin::Transaction> = + SighashCache::new(&mut tx_handler.tx); + let prevouts = &match sighash_type { + Some(TapSighashType::SinglePlusAnyoneCanPay) + | Some(TapSighashType::AllPlusAnyoneCanPay) + | Some(TapSighashType::NonePlusAnyoneCanPay) => { + bitcoin::sighash::Prevouts::One(txin_index, tx_handler.prevouts[txin_index].clone()) + } + _ => bitcoin::sighash::Prevouts::All(&tx_handler.prevouts), + }; + + let sig_hash = sighash_cache.taproot_key_spend_signature_hash( + txin_index, + prevouts, + sighash_type.unwrap_or(TapSighashType::Default), + )?; + + Ok(sig_hash) +} + +pub fn convert_tx_to_script_spend( + tx_handler: &mut TxHandler, + txin_index: usize, + script_index: usize, + sighash_type: Option, +) -> Result { + let mut sighash_cache: SighashCache<&mut bitcoin::Transaction> = + SighashCache::new(&mut tx_handler.tx); + + let prevouts = &match sighash_type { + Some(TapSighashType::SinglePlusAnyoneCanPay) + | Some(TapSighashType::AllPlusAnyoneCanPay) + | Some(TapSighashType::NonePlusAnyoneCanPay) => { + bitcoin::sighash::Prevouts::One(txin_index, tx_handler.prevouts[txin_index].clone()) + } + _ => bitcoin::sighash::Prevouts::All(&tx_handler.prevouts), + }; + let leaf_hash = TapLeafHash::from_script( + tx_handler + .scripts + .get(txin_index) + .ok_or(BridgeError::NoScriptsForTxIn(txin_index))? + .get(script_index) + .ok_or(BridgeError::NoScriptAtIndex(script_index))?, + LeafVersion::TapScript, + ); + let sig_hash = sighash_cache.taproot_script_spend_signature_hash( + txin_index, + prevouts, + leaf_hash, + sighash_type.unwrap_or(TapSighashType::Default), + )?; + + Ok(sig_hash) +} + /// First iterate over operators /// For each operator, iterate over time txs /// For each time tx, create kickoff txid @@ -38,149 +102,209 @@ pub fn create_nofn_sighash_stream( network: bitcoin::Network, ) -> impl Stream> { try_stream! { - let move_txid = builder::transaction::create_move_tx( - deposit_outpoint, - nofn_xonly_pk, - bridge_amount_sats, - network, - ) - .compute_txid(); - - let operators: Vec<(secp256k1::XOnlyPublicKey, bitcoin::Address, Txid)> = - db.get_operators(None).await?; - if operators.len() < config.num_operators { - panic!("Not enough operators"); - } + let move_txid = builder::transaction::create_move_tx( + deposit_outpoint, + nofn_xonly_pk, + bridge_amount_sats, + network, + ) + .compute_txid(); - for (operator_idx, (operator_xonly_pk, _operator_reimburse_address, collateral_funding_txid)) in - operators.iter().enumerate() - { - // Get watchtower Winternitz pubkeys for this operator. - let watchtower_challenge_wotss = (0..config.num_watchtowers) - .map(|i| db.get_watchtower_winternitz_public_keys(None, i as u32, operator_idx as u32)) - .collect::>(); - let watchtower_challenge_wotss = - futures::future::try_join_all(watchtower_challenge_wotss).await?; - - let mut input_txid = *collateral_funding_txid; - let mut input_amount = collateral_funding_amount; - - for time_tx_idx in 0..config.num_time_txs { - let time_txid = builder::transaction::create_time_tx( - *operator_xonly_pk, - input_txid, - input_amount, - timeout_block_count, - max_withdrawal_time_block_count, - network, - ) - .compute_txid(); - - let kickoff_txid = builder::transaction::create_kickoff_tx( - time_txid, - nofn_xonly_pk, - *operator_xonly_pk, - move_txid, - operator_idx, - network, - ) - .compute_txid(); + let operators: Vec<(secp256k1::XOnlyPublicKey, bitcoin::Address, Txid)> = + db.get_operators(None).await?; + if operators.len() < config.num_operators { + panic!("Not enough operators"); + } - let watchtower_wots = (0..config.num_watchtowers) - .map(|i| watchtower_challenge_wotss[i][time_tx_idx].clone()) + for (operator_idx, (operator_xonly_pk, _operator_reimburse_address, collateral_funding_txid)) in + operators.iter().enumerate() + { + // Get watchtower Winternitz pubkeys for this operator. + let watchtower_challenge_wotss = (0..config.num_watchtowers) + .map(|i| db.get_watchtower_winternitz_public_keys(None, i as u32, operator_idx as u32)) .collect::>(); + let watchtower_challenge_wotss = + futures::future::try_join_all(watchtower_challenge_wotss).await?; - let mut watchtower_challenge_page_tx_handler = - builder::transaction::create_watchtower_challenge_page_txhandler( - kickoff_txid, + let mut input_txid = *collateral_funding_txid; + let mut input_amount = collateral_funding_amount; + + for time_tx_idx in 0..config.num_time_txs { + let time_txid = builder::transaction::create_time_tx( + *operator_xonly_pk, + input_txid, + input_amount, + timeout_block_count, + max_withdrawal_time_block_count, + network, + ) + .compute_txid(); + + let kickoff_txid = builder::transaction::create_kickoff_tx( + time_txid, nofn_xonly_pk, - config.num_watchtowers as u32, - watchtower_wots.clone(), + *operator_xonly_pk, + move_txid, + operator_idx, network, + ) + .compute_txid(); + + let mut challenge_tx = builder::transaction::create_challenge_txhandler( + kickoff_txid, + nofn_xonly_pk, + *operator_xonly_pk, + _operator_reimburse_address, + network ); - yield Actor::convert_tx_to_sighash_pubkey_spend( - &mut watchtower_challenge_page_tx_handler, - 0, - )?; + yield convert_tx_to_pubkey_spend( + &mut challenge_tx, + 0, + Some(bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay) + )?; - let wcp_txid = watchtower_challenge_page_tx_handler.tx.compute_txid(); + let mut happy_reimburse_tx = builder::transaction::create_happy_reimburse_txhandler( + move_txid, + kickoff_txid, + nofn_xonly_pk, + *operator_xonly_pk, + _operator_reimburse_address, + bridge_amount_sats, + network, + ); - for (i, watchtower_wots) in watchtower_wots.iter().enumerate().take(config.num_watchtowers) { - let mut watchtower_challenge_txhandler = - builder::transaction::create_watchtower_challenge_txhandler( - wcp_txid, - i, - watchtower_wots.clone(), - &[0u8; 20], - nofn_xonly_pk, - *operator_xonly_pk, - network, - ); - yield Actor::convert_tx_to_sighash_script_spend( - &mut watchtower_challenge_txhandler, - 0, + // move utxo + yield convert_tx_to_pubkey_spend( + &mut happy_reimburse_tx, 0, + None )?; + // nofn_or_nofn3week utxo + yield convert_tx_to_pubkey_spend( + &mut happy_reimburse_tx, + 2, + None + )?; + + + let watchtower_wots = (0..config.num_watchtowers) + .map(|i| watchtower_challenge_wotss[i][time_tx_idx].clone()) + .collect::>(); - let mut operator_challenge_nack_txhandler = - builder::transaction::create_operator_challenge_nack_txhandler( - watchtower_challenge_txhandler.tx.compute_txid(), - time_txid, + let mut watchtower_challenge_page_tx_handler = + builder::transaction::create_watchtower_challenge_page_txhandler( kickoff_txid, - input_amount, - &[0u8; 20], nofn_xonly_pk, - *operator_xonly_pk, + config.num_watchtowers as u32, + watchtower_wots.clone(), network, ); - yield Actor::convert_tx_to_sighash_script_spend( - &mut operator_challenge_nack_txhandler, + + yield convert_tx_to_pubkey_spend( + &mut watchtower_challenge_page_tx_handler, 0, - 1, + None, )?; - yield Actor::convert_tx_to_sighash_pubkey_spend( - &mut operator_challenge_nack_txhandler, - 1, + + let wcp_txid = watchtower_challenge_page_tx_handler.tx.compute_txid(); + + for (i, watchtower_wots) in watchtower_wots.iter().enumerate().take(config.num_watchtowers) { + let mut watchtower_challenge_txhandler = + builder::transaction::create_watchtower_challenge_txhandler( + wcp_txid, + i, + watchtower_wots.clone(), + &[0u8; 20], + nofn_xonly_pk, + *operator_xonly_pk, + network, + ); + yield convert_tx_to_script_spend( + &mut watchtower_challenge_txhandler, + 0, + 0, + None, + )?; + + let mut operator_challenge_nack_txhandler = + builder::transaction::create_operator_challenge_nack_txhandler( + watchtower_challenge_txhandler.tx.compute_txid(), + time_txid, + kickoff_txid, + input_amount, + &[0u8; 20], + nofn_xonly_pk, + *operator_xonly_pk, + network, + ); + yield convert_tx_to_script_spend( + &mut operator_challenge_nack_txhandler, + 0, + 1, + None, + )?; + yield convert_tx_to_pubkey_spend( + &mut operator_challenge_nack_txhandler, + 1, + None, + )?; + } + + let intermediate_wots = + vec![vec![vec![[0u8; 20]; 48]; NUM_INTERMEDIATE_STEPS]; config.num_time_txs]; // TODO: Fetch from db + let assert_begin_txid = builder::transaction::create_assert_begin_txhandler( + kickoff_txid, + nofn_xonly_pk, + *operator_xonly_pk, + intermediate_wots[time_tx_idx].clone(), + network, + ) + .tx + .compute_txid(); + + let mut assert_end_tx = builder::transaction::create_assert_end_txhandler( + kickoff_txid, + assert_begin_txid, + nofn_xonly_pk, + *operator_xonly_pk, + network, + ); + yield convert_tx_to_pubkey_spend( + &mut assert_end_tx, + NUM_INTERMEDIATE_STEPS, + None, )?; - } - let intermediate_wots = - vec![vec![vec![[0u8; 20]; 48]; NUM_INTERMEDIATE_STEPS]; config.num_time_txs]; // TODO: Fetch from db - let assert_begin_txid = builder::transaction::create_assert_begin_txhandler( - kickoff_txid, - nofn_xonly_pk, - *operator_xonly_pk, - intermediate_wots[time_tx_idx].clone(), - network, - ) - .tx - .compute_txid(); - - let mut assert_end_tx = builder::transaction::create_assert_end_txhandler( - kickoff_txid, - assert_begin_txid, - nofn_xonly_pk, - *operator_xonly_pk, - network, - ); - yield Actor::convert_tx_to_sighash_pubkey_spend( - &mut assert_end_tx, - NUM_INTERMEDIATE_STEPS, - )?; + let mut disprove_tx = builder::transaction::create_disprove_txhandler( + assert_end_tx.tx.compute_txid(), + time_txid, + nofn_xonly_pk, + network, + ); - let time2_tx = builder::transaction::create_time2_tx( - *operator_xonly_pk, - time_txid, - input_amount, - network, - ); + // sign for all disprove scripts + for i in 0..NUM_INTERMEDIATE_STEPS { + yield convert_tx_to_script_spend( + &mut disprove_tx, + 0, + i, + Some(bitcoin::sighash::TapSighashType::None), + )?; + } - input_txid = time2_tx.compute_txid(); - input_amount = time2_tx.output[0].value; + let time2_tx = builder::transaction::create_time2_tx( + *operator_xonly_pk, + time_txid, + input_amount, + network, + ); + + input_txid = time2_tx.compute_txid(); + input_amount = time2_tx.output[0].value; + } } } - } } pub fn create_timout_tx_sighash_stream( @@ -213,7 +337,7 @@ pub fn create_timout_tx_sighash_stream( network, ); - yield Actor::convert_tx_to_sighash_script_spend(&mut timeout_tx_handler, 0, 0)?; + yield convert_tx_to_script_spend(&mut timeout_tx_handler, 0, 0, None)?; let time2_tx = builder::transaction::create_time2_tx( operator_xonly_pk, diff --git a/core/src/builder/transaction.rs b/core/src/builder/transaction.rs index c80232f5..47c1a757 100644 --- a/core/src/builder/transaction.rs +++ b/core/src/builder/transaction.rs @@ -5,7 +5,7 @@ use super::address::create_taproot_address; use crate::builder; -use crate::constants::{NUM_DISRPOVE_SCRIPTS, NUM_INTERMEDIATE_STEPS}; +use crate::constants::{NUM_DISPROVE_SCRIPTS, NUM_INTERMEDIATE_STEPS}; use crate::{utils, EVMAddress, UTXO}; use bitcoin::address::NetworkUnchecked; use bitcoin::hashes::Hash; @@ -44,6 +44,7 @@ pub const TIME2_TX_MIN_RELAY_FEE: Amount = Amount::from_sat(350); pub const KICKOFF_INPUT_AMOUNT: Amount = Amount::from_sat(100_000); pub const OPERATOR_REIMBURSE_CONNECTOR_AMOUNT: Amount = Amount::from_sat(330); pub const ANCHOR_AMOUNT: Amount = Amount::from_sat(330); +pub const OPERATOR_CHALLENGE_AMOUNT: Amount = Amount::from_sat(200_000_000); /// Creates the `time_tx`. It will always use `input_txid`'s first vout as the input. /// @@ -324,14 +325,15 @@ pub fn create_kickoff_tx( txid: time_txid, vout: 2, }]); - let nofn_1week = builder::script::generate_relative_timelock_script(nofn_xonly_pk, 7 * 24 * 6); + let operator_1week = + builder::script::generate_relative_timelock_script(operator_xonly_pk, 7 * 24 * 6); let operator_2week = builder::script::generate_relative_timelock_script(operator_xonly_pk, 2 * 7 * 24 * 6); let nofn_3week = builder::script::generate_relative_timelock_script(nofn_xonly_pk, 3 * 7 * 24 * 6); - let (nofn_or_nofn_1week, _) = - builder::address::create_taproot_address(&[nofn_1week], Some(nofn_xonly_pk), network); + let (nofn_or_operator_1week, _) = + builder::address::create_taproot_address(&[operator_1week], Some(nofn_xonly_pk), network); let (nofn_or_operator_2week, _) = builder::address::create_taproot_address(&[operator_2week], Some(nofn_xonly_pk), network); @@ -341,12 +343,16 @@ pub fn create_kickoff_tx( let (nofn_taproot_address, _) = builder::address::create_musig2_address(nofn_xonly_pk, network); + // TODO: change to normal sats let mut tx_outs = create_tx_outs(vec![ ( KICKOFF_UTXO_AMOUNT_SATS, nofn_taproot_address.script_pubkey(), ), - (KICKOFF_UTXO_AMOUNT_SATS, nofn_or_nofn_1week.script_pubkey()), + ( + KICKOFF_UTXO_AMOUNT_SATS, + nofn_or_operator_1week.script_pubkey(), + ), ( KICKOFF_UTXO_AMOUNT_SATS, nofn_or_operator_2week.script_pubkey(), @@ -667,19 +673,19 @@ pub fn create_assert_end_txhandler( vout: 3, }); - let mut disprve_scripts = vec![]; - for _ in 0..NUM_DISRPOVE_SCRIPTS { - disprve_scripts.push(builder::script::checksig_script(nofn_xonly_pk)); // TODO: ADD actual disprove scripts here + let mut disprove_scripts = vec![]; + for _ in 0..NUM_DISPROVE_SCRIPTS { + disprove_scripts.push(builder::script::checksig_script(nofn_xonly_pk)); // TODO: ADD actual disprove scripts here } let (disprove_address, _disprove_taproot_spend_info) = builder::address::create_taproot_address( - &disprve_scripts.clone(), + &disprove_scripts.clone(), Some(nofn_xonly_pk), network, ); let tx_outs = vec![ TxOut { - value: Amount::from_sat(330), // TOOD: Hand calculate this + value: Amount::from_sat(330), // TODO: Hand calculate this script_pubkey: disprove_address.script_pubkey(), }, builder::script::anyone_can_spend_txout(), @@ -728,6 +734,172 @@ pub fn create_assert_end_txhandler( } } +pub fn create_disprove_txhandler( + assert_end_txid: Txid, + time_txid: Txid, + nofn_xonly_pk: XOnlyPublicKey, + network: bitcoin::Network, +) -> TxHandler { + let tx_ins = create_tx_ins(vec![ + OutPoint { + txid: assert_end_txid, + vout: 0, + }, + OutPoint { + txid: time_txid, + vout: 0, + }, + ]); + + let tx_outs = vec![builder::script::anyone_can_spend_txout()]; + + let disprove_tx = create_btc_tx(tx_ins, tx_outs); + + let mut disprove_scripts = vec![]; + for _ in 0..NUM_DISPROVE_SCRIPTS { + disprove_scripts.push(builder::script::checksig_script(nofn_xonly_pk)); // TODO: ADD actual disprove scripts here + } + let (disprove_address, disprove_taproot_spend_info) = + builder::address::create_taproot_address(&disprove_scripts, Some(nofn_xonly_pk), network); + let prevouts = vec![TxOut { + value: Amount::from_sat(330), // TODO: Hand calculate this + script_pubkey: disprove_address.script_pubkey(), + }]; + + TxHandler { + tx: disprove_tx, + prevouts, + scripts: vec![disprove_scripts, vec![]], + taproot_spend_infos: vec![disprove_taproot_spend_info], + } +} + +pub fn create_challenge_txhandler( + kickoff_txid: Txid, + nofn_xonly_pk: XOnlyPublicKey, + operator_xonly_pk: XOnlyPublicKey, + operator_reimbursement_address: &bitcoin::Address, + network: bitcoin::Network, +) -> TxHandler { + let tx_ins = create_tx_ins(vec![OutPoint { + txid: kickoff_txid, + vout: 1, + }]); + + let tx_outs = vec![TxOut { + value: OPERATOR_CHALLENGE_AMOUNT, + script_pubkey: operator_reimbursement_address.script_pubkey(), + }]; + + let challenge_tx = create_btc_tx(tx_ins, tx_outs); + + let operator_1week = + builder::script::generate_relative_timelock_script(operator_xonly_pk, 7 * 24 * 6); + + let (nofn_or_operator_1week, nofn_or_operator_1week_spend_info) = + builder::address::create_taproot_address( + &[operator_1week.clone()], + Some(nofn_xonly_pk), + network, + ); + + let prevouts = vec![TxOut { + script_pubkey: nofn_or_operator_1week.script_pubkey(), + value: KICKOFF_UTXO_AMOUNT_SATS, + }]; + + TxHandler { + tx: challenge_tx, + prevouts, + scripts: vec![vec![operator_1week]], + taproot_spend_infos: vec![nofn_or_operator_1week_spend_info], + } +} + +pub fn create_happy_reimburse_txhandler( + move_txid: Txid, + kickoff_txid: Txid, + nofn_xonly_pk: XOnlyPublicKey, + operator_xonly_pk: XOnlyPublicKey, + operator_reimbursement_address: &bitcoin::Address, + bridge_amount_sats: Amount, + network: bitcoin::Network, +) -> TxHandler { + let tx_ins = create_tx_ins(vec![ + OutPoint { + txid: move_txid, + vout: 0, + }, + OutPoint { + txid: kickoff_txid, + vout: 1, + }, + OutPoint { + txid: kickoff_txid, + vout: 3, + }, + ]); + let (nofn_taproot_address, nofn_taproot_spend) = + builder::address::create_taproot_address(&[], Some(nofn_xonly_pk), network); + + let anyone_can_spend_txout = builder::script::anyone_can_spend_txout(); + + let tx_outs = vec![ + TxOut { + // value in create_move_tx currently + value: bridge_amount_sats - MOVE_TX_MIN_RELAY_FEE - anyone_can_spend_txout.value, + script_pubkey: operator_reimbursement_address.script_pubkey(), + }, + anyone_can_spend_txout.clone(), + ]; + + let happy_reimburse_tx = create_btc_tx(tx_ins, tx_outs); + + let operator_1week = + builder::script::generate_relative_timelock_script(operator_xonly_pk, 7 * 24 * 6); + let nofn_3week = + builder::script::generate_relative_timelock_script(nofn_xonly_pk, 3 * 7 * 24 * 6); + + let (nofn_or_operator_1week, nofn_or_operator_1week_spend) = + builder::address::create_taproot_address( + &[operator_1week.clone()], + Some(nofn_xonly_pk), + network, + ); + + let (nofn_or_nofn_3week, nofn_or_nofn_3week_spend) = builder::address::create_taproot_address( + &[nofn_3week.clone()], + Some(nofn_xonly_pk), + network, + ); + + let prevouts = vec![ + TxOut { + script_pubkey: nofn_taproot_address.script_pubkey(), + value: bridge_amount_sats - MOVE_TX_MIN_RELAY_FEE - anyone_can_spend_txout.value, + }, + TxOut { + value: KICKOFF_UTXO_AMOUNT_SATS, + script_pubkey: nofn_or_operator_1week.script_pubkey(), + }, + TxOut { + value: KICKOFF_UTXO_AMOUNT_SATS, + script_pubkey: nofn_or_nofn_3week.script_pubkey(), + }, + ]; + + TxHandler { + tx: happy_reimburse_tx, + prevouts, + scripts: vec![vec![], vec![operator_1week], vec![nofn_3week]], + taproot_spend_infos: vec![ + nofn_taproot_spend, + nofn_or_operator_1week_spend, + nofn_or_nofn_3week_spend, + ], + } +} + pub fn create_slash_or_take_tx( deposit_outpoint: OutPoint, kickoff_utxo: UTXO, diff --git a/core/src/constants.rs b/core/src/constants.rs index de7074e9..d9931e19 100644 --- a/core/src/constants.rs +++ b/core/src/constants.rs @@ -36,4 +36,4 @@ pub type VerifierChallenge = (BlockHash, U256, u8); pub const NUM_INTERMEDIATE_STEPS: usize = 100; -pub const NUM_DISRPOVE_SCRIPTS: usize = 100; +pub const NUM_DISPROVE_SCRIPTS: usize = 100;