Skip to content

Commit

Permalink
Introduce sighash streamer (#361)
Browse files Browse the repository at this point in the history
* Introduce sighash streamer

* Add timeout tx signing

* Verify timeout tx sigs

* Save timeout tx sigs to db

* Beautify nonce gen

* Fix compilation errors, caused by the merge.

* database: Add get_timeout_tx_sigs and test for timeout_tx_sigs.

* database: Serialize signatures inside of save_timeout_tx_sigs, not outside.

* clippy: Apply new suggestions.

* builder: Remove blank spaces in sighash.

* database: Write Schnorr signatures as bytea.

* aggregator: Move create_nonce_streams to rpc crate.

* cargo: Move new deps to workspace cargo file.

* rpc: Replace deprecated pin_mut with std::pin.

* error: Change message for TonicError type.

* rpc: Move extract_pub_nonce outside of function and update it's comment.

* rpc: verifier: Code simplification.

* database: Add SignaturesDB wrapper.

---------

Co-authored-by: Ceyhun Şen <[email protected]>
  • Loading branch information
ekrembal and ceyhunsen authored Dec 2, 2024
1 parent 2ef3f6c commit 1e832d2
Show file tree
Hide file tree
Showing 19 changed files with 649 additions and 200 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ prost = "0.13.3"
tokio = { version = "1.40.0", features = [ "full" ] }
tokio-stream = { version = "0.1.16", features = [ "sync" ] }
futures = "0.3.31"
async-stream = "0.3.6"
futures-util = "0.3.31"
futures-core = "0.3.31"

# Circuits
sha2 = { version = "=0.10.8", default-features = false }
Expand Down
3 changes: 3 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ borsh = { workspace = true}
tonic = { workspace = true}
prost = { workspace = true }
tokio-stream = { workspace = true }
async-stream = { workspace = true }
futures-util = { workspace = true }
futures-core = { workspace = true }

[dev-dependencies]
serial_test = { workspace = true }
Expand Down
5 changes: 5 additions & 0 deletions core/src/aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
actor::Actor,
builder,
config::BridgeConfig,
database::Database,
errors::BridgeError,
musig2::{
aggregate_nonces, aggregate_partial_signatures, AggregateFromPublicKeys, MuSigAggNonce,
Expand Down Expand Up @@ -32,6 +33,7 @@ use tonic::transport::Uri;
/// For now, we do not have the last bit.
#[derive(Debug, Clone)]
pub struct Aggregator {
pub(crate) db: Database,
pub(crate) config: BridgeConfig,
pub(crate) nofn_xonly_pk: secp256k1::XOnlyPublicKey,
pub(crate) verifier_clients: Vec<ClementineVerifierClient<tonic::transport::Channel>>,
Expand All @@ -41,6 +43,8 @@ pub struct Aggregator {
impl Aggregator {
// #[tracing::instrument(err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
pub async fn new(config: BridgeConfig) -> Result<Self, BridgeError> {
let db = Database::new(&config).await?;

let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_musig2_pks(
config.verifiers_public_keys.clone(),
None,
Expand Down Expand Up @@ -81,6 +85,7 @@ impl Aggregator {
.unwrap();

Ok(Aggregator {
db,
config,
nofn_xonly_pk,
verifier_clients,
Expand Down
2 changes: 1 addition & 1 deletion core/src/builder/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ pub fn generate_deposit_address(

let script_timelock = builder::script::generate_relative_timelock_script(
recovery_extracted_xonly_pk,
user_takes_after,
user_takes_after as i64,
);

create_taproot_address(&[deposit_script, script_timelock], None, network)
Expand Down
1 change: 1 addition & 0 deletions core/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
pub mod address;
pub mod script;
pub mod sighash;
pub mod transaction;
4 changes: 2 additions & 2 deletions core/src/builder/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ pub fn create_musig2_and_operator_multisig_script(
/// < (`# of blocks mined after UTXO`) appears on the chain.
pub fn generate_relative_timelock_script(
actor_taproot_xonly_pk: XOnlyPublicKey, // This is the tweaked XonlyPublicKey, which appears in the script_pubkey of the address
block_count: u32,
block_count: i64,
) -> ScriptBuf {
Builder::new()
.push_int(block_count as i64)
.push_int(block_count)
.push_opcode(OP_CSV)
.push_opcode(OP_DROP)
.push_x_only_key(&actor_taproot_xonly_pk)
Expand Down
76 changes: 76 additions & 0 deletions core/src/builder/sighash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::{actor::Actor, builder, database::Database, EVMAddress};
use async_stream::stream;
use bitcoin::TapSighash;
use bitcoin::{address::NetworkUnchecked, Address, Amount, OutPoint};
use futures_core::stream::Stream;

pub fn create_nofn_sighash_stream(
_db: Database,
deposit_outpoint: OutPoint,
evm_address: EVMAddress,
recovery_taproot_address: Address<NetworkUnchecked>,
user_takes_after: u64,
nofn_xonly_pk: secp256k1::XOnlyPublicKey,
) -> impl Stream<Item = TapSighash> {
stream! {
for i in 0..10 {
let mut dummy_move_tx_handler = builder::transaction::create_move_tx_handler(
deposit_outpoint,
evm_address,
&recovery_taproot_address,
nofn_xonly_pk,
bitcoin::Network::Regtest,
user_takes_after as u32,
Amount::from_sat(i as u64 + 1000000),
);

yield Actor::convert_tx_to_sighash_script_spend(&mut dummy_move_tx_handler, 0, 0)
.unwrap();

}
}
}

pub fn create_timout_tx_sighash_stream(
operator_xonly_pk: secp256k1::XOnlyPublicKey,
collateral_funding_txid: bitcoin::Txid,
collateral_funding_amount: Amount,
timeout_block_count: i64,
max_withdrawal_time_block_count: i64,
num_time_txs: usize,
network: bitcoin::Network,
) -> impl Stream<Item = TapSighash> {
let mut input_txid = collateral_funding_txid;
let mut input_amunt = collateral_funding_amount;
stream! {
for _ in 0..num_time_txs {
let time_tx = builder::transaction::create_time_tx(
operator_xonly_pk,
input_txid,
input_amunt,
timeout_block_count,
max_withdrawal_time_block_count,
network,
);

let mut timeout_tx_handler = builder::transaction::create_timeout_tx_handler(
operator_xonly_pk,
time_tx.compute_txid(),
timeout_block_count,
network,
);

yield Actor::convert_tx_to_sighash_script_spend(&mut timeout_tx_handler, 0, 0).unwrap();

let time2_tx = builder::transaction::create_time2_tx(
operator_xonly_pk,
time_tx.compute_txid(),
input_amunt,
network,
);

input_txid = time2_tx.compute_txid();
input_amunt = time2_tx.output[0].value;
}
}
}
164 changes: 159 additions & 5 deletions core/src/builder/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ use crate::{utils, EVMAddress, UTXO};
use bitcoin::address::NetworkUnchecked;
use bitcoin::hashes::Hash;
use bitcoin::script::PushBytesBuf;
use bitcoin::Transaction;
use bitcoin::{
absolute, taproot::TaprootSpendInfo, Address, Amount, OutPoint, ScriptBuf, TxIn, TxOut, Witness,
};
use bitcoin::{Transaction, Txid};
use secp256k1::XOnlyPublicKey;

use super::address::create_taproot_address;

#[derive(Debug, Clone)]
pub struct TxHandler {
pub tx: bitcoin::Transaction,
Expand All @@ -28,8 +30,156 @@ pub const SLASH_OR_TAKE_TX_MIN_RELAY_FEE: Amount = Amount::from_sat(240);
pub const OPERATOR_TAKES_TX_MIN_RELAY_FEE: Amount = Amount::from_sat(230);
pub const KICKOFF_UTXO_AMOUNT_SATS: Amount = Amount::from_sat(100_000);

pub const TIME_TX_MIN_RELAY_FEE: Amount = Amount::from_sat(350); // TODO: Change this to correct value
pub const TIME2_TX_MIN_RELAY_FEE: Amount = Amount::from_sat(350); // TODO: Change this to correct value
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);

// Transaction Builders --------------------------------------------------------

/// Creates time tx, it will always use input txid's first vout as input
/// Output 0: Operator's Burn Connector
/// Output 1: Operator's Time Connector: timelocked utxo for operator for the entire withdrawal time
/// Output 2: Kickoff input utxo: this utxo will be the input for the kickoff tx
/// Output 3: P2Anchor: Anchor output for CPFP
pub fn create_time_tx(
operator_xonly_pk: XOnlyPublicKey,
input_txid: Txid,
input_amount: Amount,
timeout_block_count: i64,
max_withdrawal_time_block_count: i64,
network: bitcoin::Network,
) -> Transaction {
let tx_ins = create_tx_ins(vec![OutPoint {
txid: input_txid,
vout: 0,
}]);

let max_withdrawal_time_locked_script = builder::script::generate_relative_timelock_script(
operator_xonly_pk,
max_withdrawal_time_block_count,
);

let timout_block_count_locked_script =
builder::script::generate_relative_timelock_script(operator_xonly_pk, timeout_block_count);

let tx_outs = vec![
TxOut {
value: input_amount
- OPERATOR_REIMBURSE_CONNECTOR_AMOUNT
- KICKOFF_INPUT_AMOUNT
- ANCHOR_AMOUNT
- TIME_TX_MIN_RELAY_FEE,
script_pubkey: create_taproot_address(&[], Some(operator_xonly_pk), network)
.0
.script_pubkey(),
},
TxOut {
value: OPERATOR_REIMBURSE_CONNECTOR_AMOUNT,
script_pubkey: create_taproot_address(
&[max_withdrawal_time_locked_script],
None,
network,
)
.0
.script_pubkey(),
},
TxOut {
value: KICKOFF_INPUT_AMOUNT,
script_pubkey: create_taproot_address(
&[timout_block_count_locked_script],
Some(operator_xonly_pk),
network,
)
.0
.script_pubkey(),
},
builder::script::anyone_can_spend_txout(),
];

create_btc_tx(tx_ins, tx_outs)
}

pub fn create_time2_tx(
operator_xonly_pk: XOnlyPublicKey,
time_txid: Txid,
time_tx_input_amount: Amount,
network: bitcoin::Network,
) -> Transaction {
let tx_ins = create_tx_ins(vec![
OutPoint {
txid: time_txid,
vout: 0,
},
OutPoint {
txid: time_txid,
vout: 1,
},
]);

let output_script_pubkey = create_taproot_address(&[], Some(operator_xonly_pk), network)
.0
.script_pubkey();

let tx_outs = vec![
TxOut {
value: time_tx_input_amount
- OPERATOR_REIMBURSE_CONNECTOR_AMOUNT
- KICKOFF_INPUT_AMOUNT
- ANCHOR_AMOUNT
- TIME_TX_MIN_RELAY_FEE
- ANCHOR_AMOUNT
- TIME2_TX_MIN_RELAY_FEE,
script_pubkey: output_script_pubkey.clone(),
},
TxOut {
value: OPERATOR_REIMBURSE_CONNECTOR_AMOUNT,
script_pubkey: output_script_pubkey,
},
builder::script::anyone_can_spend_txout(),
];
create_btc_tx(tx_ins, tx_outs)
}

pub fn create_timeout_tx_handler(
operator_xonly_pk: XOnlyPublicKey,
time_txid: Txid,
timeout_block_count: i64,
network: bitcoin::Network,
) -> TxHandler {
let tx_ins = create_tx_ins(vec![OutPoint {
txid: time_txid,
vout: 2,
}]);

let tx_outs = vec![builder::script::anyone_can_spend_txout()];

let tx = create_btc_tx(tx_ins, tx_outs);

let timout_block_count_locked_script =
builder::script::generate_relative_timelock_script(operator_xonly_pk, timeout_block_count);

let (timeout_input_addr, ttimeout_input_taproot_spend_info) = create_taproot_address(
&[timout_block_count_locked_script.clone()],
Some(operator_xonly_pk),
network,
);

let prevouts = vec![TxOut {
value: KICKOFF_INPUT_AMOUNT,
script_pubkey: timeout_input_addr.script_pubkey(),
}];
let scripts = vec![vec![timout_block_count_locked_script]];
let taproot_spend_infos = vec![ttimeout_input_taproot_spend_info];
TxHandler {
tx,
prevouts,
scripts,
taproot_spend_infos,
}
}

/// Creates the move_tx to move the deposit.
pub fn create_move_tx(
deposit_outpoint: OutPoint,
Expand Down Expand Up @@ -191,8 +341,10 @@ pub fn create_slash_or_take_tx(
tracing::debug!("Deposit OutPoint: {:?}", deposit_outpoint);
assert!(kickoff_utxo_address.script_pubkey() == kickoff_utxo.txout.script_pubkey);
let ins = create_tx_ins(vec![kickoff_utxo.outpoint]);
let relative_timelock_script =
builder::script::generate_relative_timelock_script(operator_xonly_pk, operator_takes_after);
let relative_timelock_script = builder::script::generate_relative_timelock_script(
operator_xonly_pk,
operator_takes_after as i64,
);
let (slash_or_take_address, _) = builder::address::create_taproot_address(
&[relative_timelock_script.clone()],
Some(nofn_xonly_pk),
Expand Down Expand Up @@ -245,8 +397,10 @@ pub fn create_operator_takes_tx(
let (musig2_address, musig2_spend_info) =
builder::address::create_musig2_address(nofn_xonly_pk, network);

let relative_timelock_script =
builder::script::generate_relative_timelock_script(operator_xonly_pk, operator_takes_after);
let relative_timelock_script = builder::script::generate_relative_timelock_script(
operator_xonly_pk,
operator_takes_after as i64,
);
let (slash_or_take_address, slash_or_take_spend_info) =
builder::address::create_taproot_address(
&[relative_timelock_script.clone()],
Expand Down
10 changes: 5 additions & 5 deletions core/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ use crypto_bigint::U256;
/// For connector tree utxos, we should wait some time for any verifier to burn the branch if preimage is revealed
pub const CONNECTOR_TREE_OPERATOR_TAKES_AFTER: u16 = 1;

/// Depth of the utxo tree from the source connector utxo, it is probably equal to claim merkle tree depth
// /// Depth of the utxo tree from the source connector utxo, it is probably equal to claim merkle tree depth
// pub const CONNECTOR_TREE_DEPTH: usize = CLAIM_MERKLE_TREE_DEPTH;

/// Dust value for mempool acceptance
pub const DUST_VALUE: Amount = Amount::from_sat(1000);

/// Minimum relay fee for mempool acceptance
// /// Minimum relay fee for mempool acceptance
// pub const MIN_RELAY_FEE: Amount = Amount::from_sat(289);

/// This is temporary. to be able to set PERIOD_END_BLOCK_HEIGHTS
pub const PERIOD_BLOCK_COUNT: u32 = 50; // 10 mins for 1 block, 6 months = 6*30*24*6 = 25920

/// For deposits, every user makes a timelock to take the money back if deposit deos not happen,
/// one reason is to not spam the bridge operator
// /// For deposits, every user makes a timelock to take the money back if deposit deos not happen,
// /// one reason is to not spam the bridge operator
// pub const USER_TAKES_AFTER: u32 = 200;

/// For deposits, bridge operator does not accept the tx if it is not confirmed
// /// For deposits, bridge operator does not accept the tx if it is not confirmed
// pub const CONFIRMATION_BLOCK_COUNT: u32 = 1;

/// K_DEEP is the give time to verifier to make a proper challenge
Expand Down
Loading

0 comments on commit 1e832d2

Please sign in to comment.