Skip to content

Commit

Permalink
refactor: Uncomment new_withdrawal_sig in operator module and fix com…
Browse files Browse the repository at this point in the history
…pilation errors.
  • Loading branch information
ceyhunsen committed Feb 6, 2025
1 parent 72a8927 commit f6c5318
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 139 deletions.
34 changes: 33 additions & 1 deletion core/src/builder/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
// Currently generate_witness functions are not yet used.
#![allow(dead_code)]

use crate::EVMAddress;
use crate::{utils, EVMAddress};
use bitcoin::opcodes::OP_TRUE;
use bitcoin::script::PushBytesBuf;
use bitcoin::secp256k1::schnorr;
use bitcoin::{
opcodes::{all::*, OP_FALSE},
Expand Down Expand Up @@ -234,6 +235,37 @@ impl DepositScript {
}
}

/// Struct for withdrawal script.
pub struct WithdrawalScript(usize);

impl SpendableScript for WithdrawalScript {
fn as_any(&self) -> &dyn Any {
self
}

fn to_script_buf(&self) -> ScriptBuf {
let mut push_bytes = PushBytesBuf::new();
push_bytes
.extend_from_slice(&utils::usize_to_var_len_bytes(self.0))
.expect("Not possible to panic while converting index to slice");

Builder::new()
.push_opcode(OP_RETURN)
.push_slice(push_bytes)
.into_script()
}
}

impl WithdrawalScript {
fn generate_witness(&self, signature: schnorr::Signature) -> Witness {
Witness::from_slice(&[signature.serialize()])
}

pub fn new(index: usize) -> Self {
Self(index)
}
}

#[cfg(test)]
fn get_script_from_arr<T: SpendableScript>(
arr: &Vec<Box<dyn SpendableScript>>,
Expand Down
302 changes: 164 additions & 138 deletions core/src/operator.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
use crate::actor::{Actor, WinternitzDerivationPath};
use crate::builder::transaction::input::SpendableTxIn;
use crate::builder::transaction::output::UnspentTxOut;
use crate::builder::transaction::TxHandlerBuilder;
use crate::config::BridgeConfig;
use crate::database::Database;
use crate::errors::BridgeError;
use crate::extended_rpc::ExtendedRpc;
use crate::musig2::AggregateFromPublicKeys;
use crate::utils::ALL_BITVM_INTERMEDIATE_VARIABLES;
use bitcoin::{Amount, OutPoint, Txid, XOnlyPublicKey};
use crate::utils::{self, ALL_BITVM_INTERMEDIATE_VARIABLES, SECP};
use crate::{builder, UTXO};
use bitcoin::consensus::deserialize;
use bitcoin::hashes::Hash;
use bitcoin::script::PushBytesBuf;
use bitcoin::secp256k1::{schnorr, Message};
use bitcoin::{Amount, OutPoint, Sequence, Transaction, TxOut, Txid, XOnlyPublicKey};
use bitcoincore_rpc::RpcApi;
use bitvm::signatures::winternitz;
use jsonrpsee::core::client::ClientT;
use jsonrpsee::http_client::HttpClientBuilder;
Expand All @@ -17,7 +26,7 @@ pub type PublicHash = [u8; 20]; // TODO: Make sure these are 20 bytes and maybe

#[derive(Debug, Clone)]
pub struct Operator {
_rpc: ExtendedRpc,
rpc: ExtendedRpc,
pub db: Database,
pub(crate) signer: Actor,
pub(crate) config: BridgeConfig,
Expand Down Expand Up @@ -101,7 +110,7 @@ impl Operator {
);

Ok(Self {
_rpc,
rpc: _rpc,
db,
signer,
config,
Expand Down Expand Up @@ -339,148 +348,165 @@ impl Operator {
// self.db.set_funding_utxo(None, funding_utxo).await
// }

// /// Checks if the withdrawal amount is within the acceptable range.
// ///
// /// # Parameters
// ///
// /// - `input_amount`:
// /// - `withdrawal_amount`:
// fn is_profitable(&self, input_amount: Amount, withdrawal_amount: Amount) -> bool {
// if withdrawal_amount
// .to_sat()
// .wrapping_sub(input_amount.to_sat())
// > self.config.bridge_amount_sats.to_sat()
// {
// return false;
// }

// // Calculate net profit after the withdrawal.
// let net_profit = self.config.bridge_amount_sats - withdrawal_amount;

// // Net profit must be bigger than withdrawal fee.
// net_profit > self.config.operator_withdrawal_fee_sats.unwrap()
// }

// /// Checks of the withdrawal has been made on Citrea, verifies a given
// /// [`bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay`] signature,
// /// checks if it is profitable and finally, funds the withdrawal.
// ///
// /// # Parameters
// ///
// /// - `withdrawal_idx`: Citrea withdrawal UTXO index
// /// - `user_sig`: User's signature that is going to be used for signing withdrawal transaction input
// /// - `input_utxo`:
// /// - `output_txout`:
// ///
// /// # Returns
// ///
// /// Withdrawal transaction's transaction id.
// #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
// async fn new_withdrawal_sig(
// &self,
// withdrawal_idx: u32,
// user_sig: schnorr::Signature,
// input_utxo: UTXO,
// output_txout: TxOut,
// ) -> Result<Txid, BridgeError> {
// if let Some(citrea_client) = &self.citrea_client {
// // See: https://gist.github.com/okkothejawa/a9379b02a16dada07a2b85cbbd3c1e80
// let params = rpc_params![
// json!({
// "to": "0x3100000000000000000000000000000000000002",
// "data": format!("0x471ba1e300000000000000000000000000000000000000000000000000000000{}",
// hex::encode(withdrawal_idx.to_be_bytes())),
// }),
// "latest"
// ];
// let response: String = citrea_client.request("eth_call", params).await?;

// let txid_response = &response[2..66];
// let txid = hex::decode(txid_response).unwrap();
// // txid.reverse(); // TODO: we should need to reverse this, test this with declareWithdrawalFiller

// let txid = Txid::from_slice(&txid).unwrap();
// if txid != input_utxo.outpoint.txid || 0 != input_utxo.outpoint.vout {
// // TODO: Fix this, vout can be different from 0 as well
// return Err(BridgeError::InvalidInputUTXO(
// txid,
// input_utxo.outpoint.txid,
// ));
// }
// }

// if !self.is_profitable(input_utxo.txout.value, output_txout.value) {
// return Err(BridgeError::NotEnoughFeeForOperator);
// }
/// Checks if the withdrawal amount is within the acceptable range.
///
/// # Parameters
///
/// - `input_amount`:
/// - `withdrawal_amount`:
fn is_profitable(
&self,
input_amount: Amount,
withdrawal_amount: Amount,
) -> Result<bool, BridgeError> {
if withdrawal_amount
.to_sat()
.wrapping_sub(input_amount.to_sat())
> self.config.bridge_amount_sats.to_sat()
{
return Ok(false);
}

// let user_xonly_pk =
// XOnlyPublicKey::from_slice(&input_utxo.txout.script_pubkey.as_bytes()[2..34])?;
// Calculate net profit after the withdrawal.
let net_profit = self.config.bridge_amount_sats - withdrawal_amount;

// Net profit must be bigger than withdrawal fee.
Ok(net_profit
> self
.config
.operator_withdrawal_fee_sats
.ok_or(BridgeError::ConfigError(
"Operator withdrawal fee sats is not specified in configuration file"
.to_string(),
))?)
}

// let tx_ins = builder::transaction::create_tx_ins(vec![input_utxo.outpoint].into());
// let tx_outs = vec![output_txout.clone()];
// let mut tx = builder::transaction::create_btc_tx(tx_ins, tx_outs);
/// Checks of the withdrawal has been made on Citrea, verifies a given
/// [`bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay`] signature,
/// checks if it is profitable and finally, funds the withdrawal.
///
/// # Parameters
///
/// - `withdrawal_idx`: Citrea withdrawal UTXO index
/// - `user_sig`: User's signature that is going to be used for signing withdrawal transaction input
/// - `input_utxo`:
/// - `output_txout`:
///
/// # Returns
///
/// Withdrawal transaction's transaction id.
#[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
pub async fn new_withdrawal_sig(
&self,
withdrawal_idx: u32,
user_sig: schnorr::Signature,
input_utxo: UTXO,
output_txout: TxOut,
) -> Result<Txid, BridgeError> {
if let Some(citrea_client) = &self.citrea_client {
// See: https://gist.github.com/okkothejawa/a9379b02a16dada07a2b85cbbd3c1e80
let params = rpc_params![
json!({
"to": "0x3100000000000000000000000000000000000002",
"data": format!("0x471ba1e300000000000000000000000000000000000000000000000000000000{}",
hex::encode(withdrawal_idx.to_be_bytes())),
}),
"latest"
];
let response: String = citrea_client.request("eth_call", params).await?;

// let mut sighash_cache = SighashCache::new(&tx);
// let sighash = sighash_cache.taproot_key_spend_signature_hash(
// 0,
// &bitcoin::sighash::Prevouts::One(0, &input_utxo.txout),
// bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay,
// )?;
let txid_response = &response[2..66];
let txid = hex::decode(txid_response).map_err(|e| BridgeError::Error(e.to_string()))?;
// txid.reverse(); // TODO: we should need to reverse this, test this with declareWithdrawalFiller

// let user_sig_wrapped = bitcoin::taproot::Signature {
// signature: user_sig,
// sighash_type: bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay,
// };
// tx.input[0].witness.push(user_sig_wrapped.serialize());
let txid = Txid::from_slice(&txid)?;
if txid != input_utxo.outpoint.txid || 0 != input_utxo.outpoint.vout {
// TODO: Fix this, vout can be different from 0 as well
return Err(BridgeError::InvalidInputUTXO(
txid,
input_utxo.outpoint.txid,
));
}
}

// SECP.verify_schnorr(
// &user_sig,
// &Message::from_digest(*sighash.as_byte_array()),
// &user_xonly_pk,
// )?;
if !self.is_profitable(input_utxo.txout.value, output_txout.value)? {
return Err(BridgeError::NotEnoughFeeForOperator);
}

// let mut push_bytes = PushBytesBuf::new();
// push_bytes
// .extend_from_slice(&utils::usize_to_var_len_bytes(self.idx))
// .unwrap();
// let op_return_txout = builder::script::op_return_txout(push_bytes);

// tx.output.push(op_return_txout.clone());

// let funded_tx = self
// .rpc
// .client
// .fund_raw_transaction(
// &tx,
// Some(&bitcoincore_rpc::json::FundRawTransactionOptions {
// add_inputs: Some(true),
// change_address: None,
// change_position: Some(1),
// change_type: None,
// include_watching: None,
// lock_unspents: None,
// fee_rate: None,
// subtract_fee_from_outputs: None,
// replaceable: None,
// conf_target: None,
// estimate_mode: None,
// }),
// None,
// )
// .await?
// .hex;
let user_xonly_pk =
XOnlyPublicKey::from_slice(&input_utxo.txout.script_pubkey.as_bytes()[2..34])?;

// let signed_tx: Transaction = deserialize(
// &self
// .rpc
// .client
// .sign_raw_transaction_with_wallet(&funded_tx, None, None)
// .await?
// .hex,
// )?;
// let user_sig_wrapped = bitcoin::taproot::Signature {
// signature: user_sig,
// sighash_type: bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay,
// };
// tx.input[0].witness.push(user_sig_wrapped.serialize());

// Ok(self.rpc.client.send_raw_transaction(&signed_tx).await?)
// }
let prevout = self
.rpc
.get_txout_from_outpoint(&input_utxo.outpoint)
.await?;
let txin = SpendableTxIn::new(input_utxo.outpoint, prevout, vec![], None);

let txout = UnspentTxOut::new(output_txout.clone(), vec![], None);
let mut push_bytes = PushBytesBuf::new();
push_bytes
.extend_from_slice(&utils::usize_to_var_len_bytes(self.idx))
.expect("Not possible to panic while converting index to slice");
let op_return_txout = builder::transaction::op_return_txout(push_bytes);
let op_return_txout = UnspentTxOut::from_partial(op_return_txout);

let tx_handler_builder = TxHandlerBuilder::new()
.add_input(txin, Sequence::from_height(0))
.add_output(txout)
.add_output(op_return_txout)
.finalize();

let sighash = tx_handler_builder.calculate_pubkey_spend_sighash(
0,
Some(bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay),
)?;

SECP.verify_schnorr(
&user_sig,
&Message::from_digest(*sighash.as_byte_array()),
&user_xonly_pk,
)?;

let funded_tx = self
.rpc
.client
.fund_raw_transaction(
tx_handler_builder.get_cached_tx(),
Some(&bitcoincore_rpc::json::FundRawTransactionOptions {
add_inputs: Some(true),
change_address: None,
change_position: Some(1),
change_type: None,
include_watching: None,
lock_unspents: None,
fee_rate: None,
subtract_fee_from_outputs: None,
replaceable: None,
conf_target: None,
estimate_mode: None,
}),
None,
)
.await?
.hex;

let signed_tx: Transaction = deserialize(
&self
.rpc
.client
.sign_raw_transaction_with_wallet(&funded_tx, None, None)
.await?
.hex,
)?;

Ok(self.rpc.client.send_raw_transaction(&signed_tx).await?)
}

/// Checks Citrea if a withdrawal is finalized.
///
Expand Down

0 comments on commit f6c5318

Please sign in to comment.