@@ -7,6 +7,7 @@ use anyhow::Context;
77use bitcoin:: { TapSighashType , Witness } ;
88use musig2:: { CompactSignature , PartialSignature } ;
99use reqwest:: { header, Client , StatusCode } ;
10+ use sha2:: { Digest , Sha256 } ;
1011use tokio:: sync:: watch;
1112use via_btc_client:: traits:: { BitcoinOps , Serializable } ;
1213use via_musig2:: {
@@ -234,7 +235,9 @@ impl ViaWithdrawalVerifier {
234235 Ok ( ( ) )
235236 }
236237
237- fn create_request_headers ( & self ) -> anyhow:: Result < header:: HeaderMap > {
238+ /// Creates authenticated request headers with body hash for replay protection
239+ /// The body hash is included in the signed payload
240+ fn create_request_headers_with_body ( & self , body : & [ u8 ] ) -> anyhow:: Result < header:: HeaderMap > {
238241 let mut headers = header:: HeaderMap :: new ( ) ;
239242 let timestamp = chrono:: Utc :: now ( ) . timestamp ( ) . to_string ( ) ;
240243 let signer = get_signer_with_merkle_root (
@@ -248,11 +251,15 @@ impl ViaWithdrawalVerifier {
248251 let private_key = bitcoin:: PrivateKey :: from_wif ( & self . wallet . private_key ) ?;
249252 let secret_key = private_key. inner ;
250253
251- // Sign timestamp + verifier_index + sequencer_version as a JSON object
254+ // Compute SHA-256 hash of the request body for replay protection
255+ let body_hash = hex:: encode ( Sha256 :: digest ( body) ) ;
256+
257+ // Sign timestamp + verifier_index + sequencer_version + body_hash
252258 let payload = serde_json:: json!( {
253259 "timestamp" : timestamp,
254260 "verifier_index" : verifier_index,
255- "sequencer_version" : sequencer_version
261+ "sequencer_version" : sequencer_version,
262+ "body_hash" : body_hash
256263 } ) ;
257264 let signature = crate :: auth:: sign_request ( & payload, & secret_key) ?;
258265
@@ -266,10 +273,20 @@ impl ViaWithdrawalVerifier {
266273 "X-Sequencer-Version" ,
267274 header:: HeaderValue :: from_str ( & sequencer_version) ?,
268275 ) ;
276+ headers. insert (
277+ "X-Body-Hash" ,
278+ header:: HeaderValue :: from_str ( & body_hash) ?,
279+ ) ;
269280
270281 Ok ( headers)
271282 }
272283
284+ /// Creates authenticated request headers GET requests (empty body)
285+ /// Get requests that have no body to use as an empty body hash
286+ fn create_request_headers ( & self ) -> anyhow:: Result < header:: HeaderMap > {
287+ self . create_request_headers_with_body ( & [ ] )
288+ }
289+
273290 async fn get_session ( & self ) -> anyhow:: Result < SigningSessionResponse > {
274291 let url = format ! ( "{}/session" , self . verifier_config. coordinator_http_url) ;
275292 let headers = self . create_request_headers ( ) ?;
@@ -336,17 +353,20 @@ impl ViaWithdrawalVerifier {
336353 nonce_map. insert ( * input_index, nonce_pair) ;
337354 }
338355
356+ let body = serde_json:: to_vec ( & nonce_map) ?;
357+
339358 let url = format ! (
340359 "{}/session/nonce" ,
341360 self . verifier_config. coordinator_http_url
342361 ) ;
343- let headers = self . create_request_headers ( ) ?;
362+ let headers = self . create_request_headers_with_body ( & body ) ?;
344363
345364 let res = self
346365 . client
347366 . post ( & url)
348367 . headers ( headers. clone ( ) )
349- . json ( & nonce_map)
368+ . body ( body)
369+ . header ( header:: CONTENT_TYPE , "application/json" )
350370 . send ( )
351371 . await ?;
352372
@@ -473,19 +493,23 @@ impl ViaWithdrawalVerifier {
473493 sig_pair_per_input. insert ( input_index, encoded) ;
474494 }
475495
496+ // Serialize body first for body hash computation
497+ let body = serde_json:: to_vec ( & sig_pair_per_input) ?;
498+
476499 let url = format ! (
477500 "{}/session/signature" ,
478501 self . verifier_config. coordinator_http_url
479502 ) ;
480- let headers = self . create_request_headers ( ) ?;
503+ let headers = self . create_request_headers_with_body ( & body ) ?;
481504
482505 tracing:: debug!( "Submitting all partial signatures to {}" , url) ;
483506
484507 let response = self
485508 . client
486509 . post ( & url)
487510 . headers ( headers. clone ( ) )
488- . json ( & sig_pair_per_input)
511+ . body ( body)
512+ . header ( header:: CONTENT_TYPE , "application/json" )
489513 . send ( )
490514 . await ?;
491515
@@ -535,13 +559,16 @@ impl ViaWithdrawalVerifier {
535559 }
536560
537561 async fn create_new_session ( & mut self ) -> anyhow:: Result < ( ) > {
562+ let body = Vec :: new ( ) ;
563+
538564 let url = format ! ( "{}/session/new" , self . verifier_config. coordinator_http_url) ;
539- let headers = self . create_request_headers ( ) ?;
565+ let headers = self . create_request_headers_with_body ( & body ) ?;
540566 let resp = self
541567 . client
542568 . post ( & url)
543569 . headers ( headers. clone ( ) )
544570 . header ( header:: CONTENT_TYPE , "application/json" )
571+ . body ( body)
545572 . send ( )
546573 . await ?;
547574
0 commit comments