Skip to content

Commit

Permalink
feat(bridge-exec)!: aggregate_[deposit|withdraw]_sig
Browse files Browse the repository at this point in the history
  • Loading branch information
storopoli committed Sep 25, 2024
1 parent 8089e12 commit ae9eec2
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 18 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/bridge-exec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ rust.unused_must_use = "deny"
rustdoc.all = "warn"

[dependencies]
alpen-express-btcio = { workspace = true }
alpen-express-db = { workspace = true }
alpen-express-primitives = { workspace = true }
alpen-express-rpc-api = { workspace = true, features = ["client"] }
express-bridge-sig-manager = { workspace = true }
Expand Down
96 changes: 87 additions & 9 deletions crates/bridge-exec/src/deposit_handler/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
use std::sync::Arc;

use alpen_express_btcio::rpc::traits::Signer;
use alpen_express_db::entities::bridge_tx_state::BridgeTxState;
use alpen_express_primitives::bridge::OperatorPartialSig;
use alpen_express_rpc_api::AlpenApiClient;
use bitcoin::secp256k1::{schnorr::Signature, SECP256K1};
use bitcoin::{
secp256k1::{schnorr::Signature, SECP256K1},
Txid,
};
use express_bridge_sig_manager::manager::SignatureManager;
use express_bridge_tx_builder::{deposit::DepositInfo, prelude::*, TxKind};
use express_storage::ops::bridge::BridgeTxStateOps;
Expand Down Expand Up @@ -124,12 +128,86 @@ pub async fn sign_deposit_tx(
}
}

Check warning on line 129 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L129

Added line #L129 was not covered by tests

/// Add the signature to the already accumulated set of signatures for a deposit transaction and
/// produce the aggregated signature if all operators have signed. Also update the database
/// entry with the signatures accumulated so far.
//
// TODO: this method will also accept a `BridgeMessage` that holds the signature attached to a
// particular deposit info by other operators.
pub async fn aggregate_signature() -> DepositExecResult<Option<Signature>> {
unimplemented!()
/// Aggregate the received signature with the ones already accumulated
/// into a fully signed [`Signature`].
///
/// This is executed by the bridge operator that is assigned the given deposit.
///
/// Returns [`None`] if the transaction in [`BridgeTxStateOps`] is not fully signed
/// by all remaining operators.
pub async fn aggregate_signature(
txid: &Txid,
sig: &OperatorPartialSig,
db_ops: &Arc<BridgeTxStateOps>,
) -> DepositExecResult<Option<Signature>> {

Check warning on line 142 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L138-L142

Added lines #L138 - L142 were not covered by tests
// setup logging
let span = span!(
Level::INFO,

Check warning on line 145 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L144-L145

Added lines #L144 - L145 were not covered by tests
"starting deposit transaction signature aggregation"
);
let _guard = span.enter();

Check warning on line 148 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L148

Added line #L148 was not covered by tests

// aggregates using MuSig2 the OperatorPartialSig into the BridgeStateOps
// checks if is fully complete
let mut tx_state = get_tx_state_by_txid(db_ops, txid).await?;

Check warning on line 152 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L152

Added line #L152 was not covered by tests

event!(
Level::DEBUG,

Check warning on line 155 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L154-L155

Added lines #L154 - L155 were not covered by tests
event = "got an updated transaction state",
%txid,
?tx_state
);

let is_fully_signed = tx_state
.add_signature(*sig)
.map_err(|e| DepositExecError::Execution(e.to_string()))?;

Check warning on line 163 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L161-L163

Added lines #L161 - L163 were not covered by tests

event!(
Level::INFO,

Check warning on line 166 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L165-L166

Added lines #L165 - L166 were not covered by tests
event = "transaction is or isn't fully signed",
%is_fully_signed
);

if is_fully_signed {

Check warning on line 171 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L171

Added line #L171 was not covered by tests
// get a new up-to-date transaction state
let tx_state = get_tx_state_by_txid(db_ops, txid).await?;
let sig = tx_state
.aggregate_signature()
.map_err(|e| DepositExecError::Execution(e.to_string()))?;

Check warning on line 176 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L173-L176

Added lines #L173 - L176 were not covered by tests

event!(
Level::INFO,

Check warning on line 179 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L178-L179

Added lines #L178 - L179 were not covered by tests
event = "aggregated final signature",
%sig
);

Ok(Some(sig))

Check warning on line 184 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L184

Added line #L184 was not covered by tests
} else {
event!(
Level::WARN,

Check warning on line 187 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L186-L187

Added lines #L186 - L187 were not covered by tests
event = "could not aggregate final signature, missing partial sigs",
);
Ok(None)

Check warning on line 190 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L190

Added line #L190 was not covered by tests
}
}

/// Gets the [`BridgeTxState`] by [`Txid`].
///
/// Errors if cannot find the `txid` in the database,
/// or if the database returns an error.
async fn get_tx_state_by_txid(
db_ops: &Arc<BridgeTxStateOps>,
txid: &Txid,
) -> DepositExecResult<BridgeTxState> {
let tx_state = db_ops
.get_tx_state_async(*txid)
.await
.map_err(|e| DepositExecError::Execution(e.to_string()))?;

Check warning on line 205 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L198-L205

Added lines #L198 - L205 were not covered by tests

match tx_state {
Some(tx_state) => Ok(tx_state),
None => Err(DepositExecError::InvalidRequest(
"could not find a transaction state for txid: {txid}".to_string(),
)),

Check warning on line 211 in crates/bridge-exec/src/deposit_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/deposit_handler/handler.rs#L207-L211

Added lines #L207 - L211 were not covered by tests
}
}
85 changes: 79 additions & 6 deletions crates/bridge-exec/src/withdrawal_handler/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,86 @@ pub async fn sign_withdraw_tx(
}
}

/// Aggregate the received signature with the ones already accumulated.
/// Aggregate the received signature with the ones already accumulated
/// into a fully signed [`Signature`].
///
/// This is executed by the bridge operator that is assigned the given withdrawal.
// TODO: pass in a database client once the database traits have been implemented.
pub async fn aggregate_withdrawal_sig(
_withdrawal_info: &CooperativeWithdrawalInfo,
_sig: &OperatorPartialSig,
///
/// Returns [`None`] if the transaction in [`BridgeTxStateOps`] is not fully signed
/// by all remaining operators.
pub async fn aggregate_withdraw_sig(
txid: &Txid,
sig: &OperatorPartialSig,
db_ops: &Arc<BridgeTxStateOps>,

Check warning on line 141 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L138-L141

Added lines #L138 - L141 were not covered by tests
) -> WithdrawalExecResult<Option<Signature>> {
unimplemented!()
// setup logging
let span = span!(
Level::INFO,

Check warning on line 145 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L144-L145

Added lines #L144 - L145 were not covered by tests
"starting withdrawal transaction signature aggregation"
);
let _guard = span.enter();

Check warning on line 148 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L148

Added line #L148 was not covered by tests

// aggregates using MuSig2 the OperatorPartialSig into the BridgeStateOps
// checks if is fully complete
let mut tx_state = get_tx_state_by_txid(db_ops, txid).await?;

Check warning on line 152 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L152

Added line #L152 was not covered by tests

event!(
Level::DEBUG,

Check warning on line 155 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L154-L155

Added lines #L154 - L155 were not covered by tests
event = "got an updated transaction state",
%txid,
?tx_state
);

let is_fully_signed = tx_state
.add_signature(*sig)
.map_err(|e| WithdrawalExecError::Execution(e.to_string()))?;

Check warning on line 163 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L161-L163

Added lines #L161 - L163 were not covered by tests

event!(
Level::INFO,

Check warning on line 166 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L165-L166

Added lines #L165 - L166 were not covered by tests
event = "transaction is or isn't fully signed",
%is_fully_signed
);

if is_fully_signed {

Check warning on line 171 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L171

Added line #L171 was not covered by tests
// get a new up-to-date transaction state
let tx_state = get_tx_state_by_txid(db_ops, txid).await?;
let sig = tx_state
.aggregate_signature()
.map_err(|e| WithdrawalExecError::Execution(e.to_string()))?;

Check warning on line 176 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L173-L176

Added lines #L173 - L176 were not covered by tests

event!(
Level::INFO,

Check warning on line 179 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L178-L179

Added lines #L178 - L179 were not covered by tests
event = "aggregated final signature",
%sig
);

Ok(Some(sig))

Check warning on line 184 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L184

Added line #L184 was not covered by tests
} else {
event!(
Level::WARN,

Check warning on line 187 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L186-L187

Added lines #L186 - L187 were not covered by tests
event = "could not aggregate final signature, missing partial sigs",
);
Ok(None)

Check warning on line 190 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L190

Added line #L190 was not covered by tests
}
}

Check warning on line 192 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L192

Added line #L192 was not covered by tests

/// Gets the [`BridgeTxState`] by [`Txid`].
///
/// Errors if cannot find the `txid` in the database,
/// or if the database returns an error.
async fn get_tx_state_by_txid(
db_ops: &Arc<BridgeTxStateOps>,
txid: &Txid,
) -> WithdrawalExecResult<BridgeTxState> {
let tx_state = db_ops
.get_tx_state_async(*txid)
.await
.map_err(|e| WithdrawalExecError::Execution(e.to_string()))?;

Check warning on line 205 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L198-L205

Added lines #L198 - L205 were not covered by tests

match tx_state {
Some(tx_state) => Ok(tx_state),
None => Err(WithdrawalExecError::InvalidRequest(
"could not find a transaction state for txid: {txid}".to_string(),
)),

Check warning on line 211 in crates/bridge-exec/src/withdrawal_handler/handler.rs

View check run for this annotation

Codecov / codecov/patch

crates/bridge-exec/src/withdrawal_handler/handler.rs#L207-L211

Added lines #L207 - L211 were not covered by tests
}
}
24 changes: 22 additions & 2 deletions crates/db/src/entities/bridge_tx_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ use alpen_express_primitives::{
l1::{BitcoinPsbt, TaprootSpendPath},
};
use arbitrary::Arbitrary;
use bitcoin::{Transaction, TxOut, Txid};
use bitcoin::{hashes::Hash, Transaction, TxOut, Txid};
use borsh::{BorshDeserialize, BorshSerialize};
use musig2::{PartialSignature, PubNonce};
use musig2::{
aggregate_partial_signatures, secp256k1::schnorr::Signature, AggNonce, KeyAggContext,
PartialSignature, PubNonce,
};

use super::errors::{BridgeTxStateError, EntityResult};

Expand Down Expand Up @@ -210,6 +213,23 @@ impl BridgeTxState {
pub fn ordered_sigs(&self) -> impl Iterator<Item = PartialSignature> + '_ {
self.collected_sigs.values().map(|v| *v.inner())
}

/// Aggregates and extracts the underlying partial signatures as a [`Signature`].
pub fn aggregate_signature(&self) -> EntityResult<Signature> {
let message = *self.compute_txid().as_byte_array();
let key_agg_context: KeyAggContext = self
.pubkey_table
.clone()
.try_into()
.map_err(|_| BridgeTxStateError::Unauthorized)?;
let pub_nonces = self.collected_nonces.values().map(|n| n.inner().clone());
let aggregate_nonce = AggNonce::sum(pub_nonces);
let partial_sigs = self.collected_sigs.values().map(|s| *s.inner());
let sig: Signature =
aggregate_partial_signatures(&key_agg_context, &aggregate_nonce, partial_sigs, message)
.map_err(|_| BridgeTxStateError::Unauthorized)?;
Ok(sig)
}

Check warning on line 232 in crates/db/src/entities/bridge_tx_state.rs

View check run for this annotation

Codecov / codecov/patch

crates/db/src/entities/bridge_tx_state.rs#L218-L232

Added lines #L218 - L232 were not covered by tests
}

#[cfg(test)]
Expand Down
13 changes: 12 additions & 1 deletion crates/primitives/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use bitcoin::{
secp256k1::{PublicKey, SecretKey},
};
use borsh::{BorshDeserialize, BorshSerialize};
use musig2::{BinaryEncoding, NonceSeed, PartialSignature, PubNonce, SecNonce};
use musig2::{
errors::KeyAggError, BinaryEncoding, KeyAggContext, NonceSeed, PartialSignature, PubNonce,
SecNonce,
};
use serde::{Deserialize, Serialize};

use crate::{
Expand Down Expand Up @@ -45,6 +48,14 @@ impl From<PublickeyTable> for Vec<PublicKey> {
}
}

impl TryFrom<PublickeyTable> for KeyAggContext {
type Error = KeyAggError;

fn try_from(value: PublickeyTable) -> Result<Self, Self::Error> {
KeyAggContext::new(Into::<Vec<PublicKey>>::into(value))
}

Check warning on line 56 in crates/primitives/src/bridge.rs

View check run for this annotation

Codecov / codecov/patch

crates/primitives/src/bridge.rs#L54-L56

Added lines #L54 - L56 were not covered by tests
}

impl BorshSerialize for PublickeyTable {
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
// Serialize the length of the BTreeMap
Expand Down

0 comments on commit ae9eec2

Please sign in to comment.