Skip to content

Commit

Permalink
watchtower pk's for watchtower challenge page tx (#404)
Browse files Browse the repository at this point in the history
* Add watchtower xonly_pk to get_params and save to db

* fix clippy error

* add db helper to get single xonly_pk, remove unwrap

* Adjust sighash.rs and transaction.rs to remove nofn sigs from challenge page tx

* Incorporate Ceyhun's feedback
  • Loading branch information
atacann authored Jan 10, 2025
1 parent 365f6b2 commit bb6c35f
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 12 deletions.
12 changes: 4 additions & 8 deletions core/src/builder/sighash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ pub fn create_nofn_sighash_stream(
panic!("Not enough operators");
}

let watchtower_pks = db.get_all_watchtowers_xonly_pks(None).await?;

for (operator_idx, (operator_xonly_pk, _operator_reimburse_address, collateral_funding_txid)) in
operators.iter().enumerate()
{
Expand Down Expand Up @@ -188,8 +190,8 @@ pub fn create_nofn_sighash_stream(
let mut watchtower_challenge_page_tx_handler =
builder::transaction::create_watchtower_challenge_page_txhandler(
&kickoff_txhandler,
nofn_xonly_pk,
config.num_watchtowers as u32,
&watchtower_pks,
watchtower_wots.clone(),
network,
);
Expand All @@ -201,7 +203,7 @@ pub fn create_nofn_sighash_stream(
)?;

for i in 0..config.num_watchtowers {
let mut watchtower_challenge_txhandler =
let watchtower_challenge_txhandler =
builder::transaction::create_watchtower_challenge_txhandler(
&watchtower_challenge_page_tx_handler,
i,
Expand All @@ -210,12 +212,6 @@ pub fn create_nofn_sighash_stream(
*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(
Expand Down
4 changes: 2 additions & 2 deletions core/src/builder/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,8 +454,8 @@ pub fn create_kickoff_txhandler(
/// Creates a [`TxHandler`] for the watchtower challenge page transaction.
pub fn create_watchtower_challenge_page_txhandler(
kickoff_tx_handler: &TxHandler,
nofn_xonly_pk: XOnlyPublicKey,
num_watchtowers: u32,
watchtower_xonly_pks: &[XOnlyPublicKey],
watchtower_wots: Vec<Vec<[u8; 20]>>,
network: bitcoin::Network,
) -> TxHandler {
Expand All @@ -475,7 +475,7 @@ pub fn create_watchtower_challenge_page_txhandler(
.map(|i| {
let mut x =
verifier.checksig_verify(&wots_params, watchtower_wots[i as usize].as_ref());
x = x.push_x_only_key(&nofn_xonly_pk);
x = x.push_x_only_key(&watchtower_xonly_pks[i as usize]);
x = x.push_opcode(OP_CHECKSIG); // TODO: Add checksig in the beginning
let x = x.compile();
let (watchtower_challenge_addr, watchtower_challenge_spend) =
Expand Down
101 changes: 100 additions & 1 deletion core/src/database/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use bitcoin::{Address, OutPoint, Txid};
use bitvm::bridge::transactions::signing_winternitz::WinternitzPublicKey;
use bitvm::signatures::winternitz;
use risc0_zkvm::Receipt;
use secp256k1::schnorr;
use secp256k1::{schnorr, XOnlyPublicKey};
use sqlx::{Postgres, QueryBuilder};

impl Database {
Expand Down Expand Up @@ -1078,6 +1078,70 @@ impl Database {

Ok(watchtower_winternitz_public_keys)
}

/// Sets xonly public key of a watchtoer.
#[tracing::instrument(skip(self, tx), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
pub async fn save_watchtower_xonly_pk(
&self,
tx: Option<&mut sqlx::Transaction<'_, Postgres>>,
watchtower_id: u32,
xonly_pk: &XOnlyPublicKey,
) -> Result<(), BridgeError> {
let query = sqlx::query(
"INSERT INTO watchtower_xonly_public_keys (watchtower_id, xonly_pk) VALUES ($1, $2);",
)
.bind(watchtower_id as i64)
.bind(xonly_pk.serialize());

match tx {
Some(tx) => query.execute(&mut **tx).await,
None => query.execute(&self.connection).await,
}?;

Ok(())
}

/// Gets xonly public keys of all watchtowers.
#[tracing::instrument(skip(self, tx), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
pub async fn get_all_watchtowers_xonly_pks(
&self,
tx: Option<&mut sqlx::Transaction<'_, Postgres>>,
) -> Result<Vec<XOnlyPublicKey>, BridgeError> {
let query = sqlx::query_as(
"SELECT xonly_pk FROM watchtower_xonly_public_keys ORDER BY watchtower_id;",
);

let rows: Vec<(Vec<u8>,)> = match tx {
Some(tx) => query.fetch_all(&mut **tx).await,
None => query.fetch_all(&self.connection).await,
}?;

rows.into_iter()
.map(|xonly_pk| {
XOnlyPublicKey::from_slice(&xonly_pk.0).map_err(BridgeError::Secp256k1Error)
})
.collect()
}

/// Gets xonly public key of a single watchtower
#[tracing::instrument(skip(self, tx), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
pub async fn get_watchtower_xonly_pk(
&self,
tx: Option<&mut sqlx::Transaction<'_, Postgres>>,
watchtower_id: u32,
) -> Result<XOnlyPublicKey, BridgeError> {
let query = sqlx::query_as(
"SELECT xonly_pk FROM watchtower_xonly_public_keys WHERE watchtower_id = $1;",
)
.bind(watchtower_id as i64);

let xonly_key: (Vec<u8>,) = match tx {
Some(tx) => query.fetch_one(&mut **tx).await,
None => query.fetch_one(&self.connection).await,
}?;

Ok(XOnlyPublicKey::from_slice(&xonly_key.0)?)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -1894,4 +1958,39 @@ mod tests {
assert_eq!(wpk0, read_wpks[0]);
assert_eq!(wpk1, read_wpks[1]);
}
#[tokio::test]
async fn save_get_watchtower_xonly_pk() {
let config = create_test_config_with_thread_name!(None);
let database = Database::new(&config).await.unwrap();

use secp256k1::{rand, Keypair, Secp256k1, XOnlyPublicKey};

let secp = Secp256k1::new();
let keypair1 = Keypair::new(&secp, &mut rand::thread_rng());
let xonly1 = XOnlyPublicKey::from_keypair(&keypair1).0;

let keypair2 = Keypair::new(&secp, &mut rand::thread_rng());
let xonly2 = XOnlyPublicKey::from_keypair(&keypair2).0;

let w_data = vec![xonly1, xonly2];

for (id, data) in w_data.iter().enumerate() {
database
.save_watchtower_xonly_pk(None, id as u32, data)
.await
.unwrap();
}

let read_pks = database.get_all_watchtowers_xonly_pks(None).await.unwrap();

assert_eq!(read_pks, w_data);

for (id, key) in w_data.iter().enumerate() {
let read_pk = database
.get_watchtower_xonly_pk(None, id as u32)
.await
.unwrap();
assert_eq!(read_pk, *key);
}
}
}
2 changes: 2 additions & 0 deletions core/src/rpc/clementine.proto
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ message WatchtowerParams {
uint32 watchtower_id = 1;
// Flattened list of Winternitz pubkeys for each operator's timetxs.
repeated WinternitzPubkey winternitz_pubkeys = 2;
// xonly public key serialized to bytes
bytes xonly_pk = 3;
}

// Watchtowers are responsible for challenging the operator's kickoff txs.
Expand Down
3 changes: 3 additions & 0 deletions core/src/rpc/clementine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ pub struct WatchtowerParams {
/// Flattened list of Winternitz pubkeys for each operator's timetxs.
#[prost(message, repeated, tag = "2")]
pub winternitz_pubkeys: ::prost::alloc::vec::Vec<WinternitzPubkey>,
/// xonly public key serialized to bytes
#[prost(bytes = "vec", tag = "3")]
pub xonly_pk: ::prost::alloc::vec::Vec<u8>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RawSignedMoveTx {
Expand Down
9 changes: 8 additions & 1 deletion core/src/rpc/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use bitvm::{
bridge::transactions::signing_winternitz::WinternitzPublicKey, signatures::winternitz,
};
use futures::StreamExt;
use secp256k1::{schnorr, Message};
use secp256k1::{schnorr, Message, XOnlyPublicKey};
use std::{pin::pin, str::FromStr};
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
Expand Down Expand Up @@ -211,6 +211,13 @@ impl ClementineVerifier for Verifier {
.await?;
}

let xonly_pk = XOnlyPublicKey::from_slice(&watchtower_params.xonly_pk).map_err(|_| {
BridgeError::RPCParamMalformed("watchtower.xonly_pk", "Invalid xonly key".to_string())
})?;
self.db
.save_watchtower_xonly_pk(None, watchtower_params.watchtower_id, &xonly_pk)
.await?;

Ok(Response::new(Empty {}))
}

Expand Down
7 changes: 7 additions & 0 deletions core/src/rpc/watchtower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ impl ClementineWatchtower for Watchtower {
.map(WinternitzPubkey::from_bitvm)
.collect::<Vec<WinternitzPubkey>>();

let xonly_pk = self.actor.xonly_public_key.serialize().to_vec();

Ok(Response::new(WatchtowerParams {
watchtower_id: self.config.index,
winternitz_pubkeys,
xonly_pk,
}))
}
}
Expand Down Expand Up @@ -76,6 +79,10 @@ mod tests {
.into_inner();

assert_eq!(params.watchtower_id, watchtower.config.index);
assert_eq!(
params.xonly_pk,
watchtower.actor.xonly_public_key.serialize().to_vec()
);
assert!(params.winternitz_pubkeys.len() == config.num_operators * config.num_time_txs);
}
}
6 changes: 6 additions & 0 deletions scripts/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ create table if not exists header_chain_proofs (
proof bytea
);

create table if not exists watchtower_xonly_public_keys (
watchtower_id int not null,
xonly_pk bytea not null,
primary key (watchtower_id)
);

-- Verifier table of watchtower Winternitz public keys for every operator and time_tx pair
create table if not exists watchtower_winternitz_public_keys (
watchtower_id int not null,
Expand Down

0 comments on commit bb6c35f

Please sign in to comment.