· 6 years ago · Mar 19, 2019, 03:22 PM
1//! Listener
2//!
3//! Listener interface and implementations
4
5use std::str::FromStr;
6use std::sync::mpsc::Sender;
7use std::sync::{Arc, Mutex};
8use std::thread;
9
10use bitcoin::consensus::serialize;
11use bitcoin::util::hash::Sha256dHash;
12use bitcoin_hashes::hex::FromHex;
13use futures::future;
14use futures::sync::oneshot;
15use hyper::rt::{self, Future, Stream};
16use hyper::service::service_fn;
17use hyper::{Body, Method, Request, Response, Server, StatusCode};
18use secp256k1::{Message, PublicKey, Secp256k1, Signature};
19use serde_json::{self, Value};
20
21use crate::challenger::{ChallengeResponse, ChallengeState};
22use crate::error::Result;
23use crate::request::Bid;
24
25/// Messsage type for challenge proofs sent by guardnodes
26#[derive(Debug)]
27pub struct ChallengeProof {
28 /// Challenge (transaction id) hash
29 pub hash: Sha256dHash,
30 /// Challenge signature for hash and pubkey
31 pub sig: Signature,
32 /// Pubkey used to generate challenge signature
33 pub bid: Bid,
34}
35
36impl ChallengeProof {
37 /// Parse serde json value into ChallengeProof struct result
38 pub fn from_json(val: Value) -> Result<ChallengeProof> {
39 let hash = Sha256dHash::from_hex(val["hash"].as_str().unwrap_or(""))?;
40 let txid = Sha256dHash::from_hex(val["txid"].as_str().unwrap_or(""))?;
41 let pubkey = PublicKey::from_str(val["pubkey"].as_str().unwrap_or(""))?;
42 let sig = Signature::from_der(&Vec::<u8>::from_hex(val["sig"].as_str().unwrap_or(""))?)?;
43 Ok(ChallengeProof {
44 hash,
45 sig,
46 bid: Bid { txid, pubkey },
47 })
48 }
49
50 /// Verify the challenge proof signature using the pubkey and challenge hash
51 fn verify(challenge_proof: &ChallengeProof) -> Result<()> {
52 let secp = Secp256k1::new();
53 secp.verify(
54 &Message::from_slice(&serialize(&challenge_proof.hash)).unwrap(),
55 &challenge_proof.sig,
56 &challenge_proof.bid.pubkey,
57 )?;
58 Ok(())
59 }
60}
61
62// Future hyper return type for listener server responses
63type BoxFut<'a> = Box<Future<Item = Response<Body>, Error = hyper::Error> + Send + 'a>;
64
65/// Handle the POST request /challengeproof. Validate body is in json format,
66/// parse this into a ChallengeProof struct and then verify that there is an
67/// active challenge, that the proof bid exists and that the sig is correct.
68/// Successful responses are pushed to the challenge response channel for the
69/// challenger to receive
70fn handle_challengeproof<'a>(
71 req: Request<Body>,
72 challenge: &'a Arc<Mutex<ChallengeState>>,
73 challenge_resp: Sender<ChallengeResponse>,
74) -> BoxFut {
75 let resp = req.into_body().concat2().map(move |body| {
76 // parse request body
77 match serde_json::from_slice::<Value>(body.as_ref()) {
78 // parse json from body
79 Ok(obj) => match ChallengeProof::from_json(obj) {
80 // parse challenge proof from json
81 Ok(proof) => {
82 // check for an active challenge
83 let challenge_lock = challenge.lock().unwrap();
84 if let Some(h) = challenge_lock.latest_challenge {
85 // check challenge proof bid exists
86 if !challenge_lock.bids.contains(&proof.bid) {
87 return response(StatusCode::BAD_REQUEST, "bad-bid".to_string());
88 }
89 // drop lock immediately
90 std::mem::drop(challenge_lock);
91 // check challenge proof hash is correct
92 if proof.hash != h {
93 return response(StatusCode::BAD_REQUEST, "bad-hash".to_string());
94 }
95 // check challenge proof sig is correct
96 if let Err(e) = ChallengeProof::verify(&proof) {
97 return response(StatusCode::BAD_REQUEST, format!("bad-sig: {}", e));
98 }
99 // send successful response to challenger
100 challenge_resp
101 .send(ChallengeResponse(proof.hash, proof.bid.clone()))
102 .unwrap();
103 return response(StatusCode::OK, String::new());
104 }
105 response(StatusCode::BAD_REQUEST, format!("no-active-challenge"))
106 }
107 Err(e) => response(StatusCode::BAD_REQUEST, format!("bad-proof-data: {}", e)),
108 },
109 Err(e) => response(StatusCode::BAD_REQUEST, format!("bad-json-data: {}", e)),
110 }
111 });
112 Box::new(resp)
113}
114
115/// Handler for the listener server. Only allows requests to /
116/// and to the /challengeproof POST uri for receiving challenges from guardnodes
117fn handle<'a>(
118 req: Request<Body>,
119 challenge: &'a Arc<Mutex<ChallengeState>>,
120 challenge_resp: &Sender<ChallengeResponse>,
121) -> BoxFut<'a> {
122 let resp = match (req.method(), req.uri().path()) {
123 (&Method::GET, "/") => response(
124 StatusCode::OK,
125 "Challenge proof should be POSTed to /challengeproof".to_string(),
126 ),
127
128 (&Method::POST, "/challengeproof") => {
129 return handle_challengeproof(req, &challenge, challenge_resp.clone());
130 }
131
132 _ => response(StatusCode::NOT_FOUND, format!("Invalid request {}", req.uri().path())),
133 };
134
135 Box::new(future::ok(resp))
136}
137
138/// Create hyper response from status code and message Body
139fn response(status: StatusCode, message: String) -> Response<Body> {
140 Response::builder()
141 .status(status)
142 .body(Body::from(format!("{}", message)))
143 .unwrap()
144}
145
146/// Run the listener server that listens to a specified address for incoming
147/// requests and passes these to handle(). The server runs in a new thread and
148/// can be shutdown via a future oneshot channel receiver from the main method
149/// of the coordinator
150pub fn run_listener(
151 challenge: Arc<Mutex<ChallengeState>>,
152 ch_resp: Sender<ChallengeResponse>,
153 ch_recv: oneshot::Receiver<()>,
154) -> thread::JoinHandle<()> {
155 let addr = ([127, 0, 0, 1], 9999).into();
156
157 let listener_service = move || {
158 let challenge = Arc::clone(&challenge);
159 let challenge_resp = ch_resp.clone();
160
161 service_fn(move |req: Request<Body>| handle(req, &challenge, &challenge_resp))
162 };
163
164 let server = Server::bind(&addr)
165 .serve(listener_service)
166 .with_graceful_shutdown(ch_recv)
167 .map_err(|e| error!("server error: {}", e));
168
169 thread::spawn(move || {
170 rt::run(server);
171 })
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use bitcoin_hashes::hex::ToHex;
178 use secp256k1::SecretKey;
179 use std::error::Error;
180 use std::sync::mpsc::{channel, Receiver, TryRecvError};
181
182 use crate::service::{MockService, Service};
183
184 /// Generate dummy hash for tests
185 fn gen_dummy_hash(i: u8) -> Sha256dHash {
186 Sha256dHash::from(&[i as u8; 32] as &[u8])
187 }
188
189 /// Geberate dummy challenge state
190 fn gen_challenge_state(request_hash: &Sha256dHash, challenge_hash: &Sha256dHash) -> ChallengeState {
191 let service = MockService::new();
192
193 let request = service.get_request(&request_hash).unwrap().unwrap();
194 let bids = service.get_request_bids(&request_hash).unwrap().unwrap();
195 ChallengeState {
196 request,
197 bids,
198 latest_challenge: Some(*challenge_hash),
199 }
200 }
201
202 #[test]
203 fn challengeproof_from_json_test() {
204 // good data
205 let data = r#"
206 {
207 "txid": "0000000000000000000000000000000000000000000000000000000000000000",
208 "pubkey": "03356190524d52d7e94e1bd43e8f23778e585a4fe1f275e65a06fa5ceedb67d111",
209 "hash": "0404040404040404040404040404040404040404040404040404040404040404",
210 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
211 }"#;
212 let proof = ChallengeProof::from_json(serde_json::from_str::<Value>(data).unwrap());
213 assert!(proof.is_ok());
214
215 // bad txid
216 let data = r#"
217 {
218 "txid": "",
219 "pubkey": "03356190524d52d7e94e1bd43e8f23778e585a4fe1f275e65a06fa5ceedb67d111",
220 "hash": "0404040404040404040404040404040404040404040404040404040404040404",
221 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
222 }"#;
223 let proof = ChallengeProof::from_json(serde_json::from_str::<Value>(data).unwrap());
224 assert_eq!(proof.err().unwrap().description(), "bitcoin hex error");
225
226 // bad pubkey
227 let data = r#"
228 {
229 "txid": "0000000000000000000000000000000000000000000000000000000000000000",
230 "pubkey": "0356190524d52d7e94e1bd43e8f23778e585a4fe1f275e65a06fa5ceedb67d111",
231 "hash": "0404040404040404040404040404040404040404040404040404040404040404",
232 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
233 }"#;
234 let proof = ChallengeProof::from_json(serde_json::from_str::<Value>(data).unwrap());
235 assert_eq!(proof.err().unwrap().description(), "secp256k1 error");
236
237 // bad hash
238 let data = r#"
239 {
240 "txid": "0000000000000000000000000000000000000000000000000000000000000000",
241 "pubkey": "03356190524d52d7e94e1bd43e8f23778e585a4fe1f275e65a06fa5ceedb67d111",
242 "hash": "04040404040404040404040404040404040404040404040404040404040404",
243 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
244 }"#;
245 let proof = ChallengeProof::from_json(serde_json::from_str::<Value>(data).unwrap());
246 assert_eq!(proof.err().unwrap().description(), "bitcoin hex error");
247
248 // bad sig
249 let data = r#"
250 {
251 "txid": "0000000000000000000000000000000000000000000000000000000000000000",
252 "pubkey": "03356190524d52d7e94e1bd43e8f23778e585a4fe1f275e65a06fa5ceedb67d111",
253 "hash": "0404040404040404040404040404040404040404040404040404040404040404",
254 "sig": "4402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
255 }"#;
256 let proof = ChallengeProof::from_json(serde_json::from_str::<Value>(data).unwrap());
257 assert_eq!(proof.err().unwrap().description(), "secp256k1 error");
258 }
259
260 #[test]
261 fn challengeproof_verify_test() {
262 let chl_hash = gen_dummy_hash(11);
263 let _challenge_state = gen_challenge_state(&gen_dummy_hash(3), &chl_hash);
264 let bid_txid = _challenge_state.bids.iter().next().unwrap().txid;
265 let bid_pubkey = _challenge_state.bids.iter().next().unwrap().pubkey;
266
267 // verify good sig
268 let secp = Secp256k1::new();
269 let secret_key = SecretKey::from_slice(&[0xaa; 32]).unwrap();
270 let sig = secp.sign(&Message::from_slice(&serialize(&chl_hash)).unwrap(), &secret_key);
271
272 let proof = ChallengeProof {
273 hash: chl_hash,
274 sig: sig,
275 bid: Bid {
276 txid: bid_txid,
277 pubkey: bid_pubkey,
278 },
279 };
280
281 let verify = ChallengeProof::verify(&proof);
282 assert!(verify.is_ok());
283
284 // verify bad sig
285 let secret_key = SecretKey::from_slice(&[0xbb; 32]).unwrap();
286 let sig = secp.sign(&Message::from_slice(&serialize(&chl_hash)).unwrap(), &secret_key);
287
288 let proof = ChallengeProof {
289 hash: chl_hash,
290 sig: sig,
291 bid: Bid {
292 txid: bid_txid,
293 pubkey: bid_pubkey,
294 },
295 };
296
297 let verify = ChallengeProof::verify(&proof);
298 assert_eq!(verify.err().unwrap().description(), "secp256k1 error");
299 }
300
301 #[test]
302 fn handle_test() {
303 let (resp_tx, resp_rx): (Sender<ChallengeResponse>, Receiver<ChallengeResponse>) = channel();
304
305 let chl_hash = gen_dummy_hash(11);
306 let _challenge_state = gen_challenge_state(&gen_dummy_hash(3), &chl_hash);
307 let bid_txid = _challenge_state.bids.iter().next().unwrap().txid;
308 let bid_pubkey = _challenge_state.bids.iter().next().unwrap().pubkey;
309 let challenge_state = Arc::new(Mutex::new(_challenge_state));
310
311 // Request get /
312 let data = "";
313 let request = Request::builder()
314 .method("GET")
315 .uri("/")
316 .body(Body::from(data))
317 .unwrap();
318 let _ = handle(request, challenge_state.clone(), resp_tx.clone())
319 .map(|res| {
320 assert_eq!(res.status(), StatusCode::OK);
321 res.into_body()
322 .concat2()
323 .map(|chunk| {
324 assert_eq!(
325 "Challenge proof should be POSTed to /challengeproof",
326 String::from_utf8_lossy(&chunk)
327 );
328 })
329 .wait()
330 })
331 .wait();
332 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
333
334 // Request get /dummy
335 let data = "";
336 let request = Request::builder()
337 .method("GET")
338 .uri("/dummy")
339 .body(Body::from(data))
340 .unwrap();
341 let _ = handle(request, challenge_state.clone(), resp_tx.clone())
342 .map(|res| {
343 assert_eq!(res.status(), StatusCode::NOT_FOUND);
344 res.into_body()
345 .concat2()
346 .map(|chunk| {
347 assert_eq!("Invalid request /dummy", String::from_utf8_lossy(&chunk));
348 })
349 .wait()
350 })
351 .wait();
352 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
353
354 // Request post /dummy
355 let data = "";
356 let request = Request::builder()
357 .method("POST")
358 .uri("/dummy")
359 .body(Body::from(data))
360 .unwrap();
361 let _ = handle(request, challenge_state.clone(), resp_tx.clone())
362 .map(|res| {
363 assert_eq!(res.status(), StatusCode::NOT_FOUND);
364 res.into_body()
365 .concat2()
366 .map(|chunk| {
367 assert_eq!("Invalid request /dummy", String::from_utf8_lossy(&chunk));
368 })
369 .wait()
370 })
371 .wait();
372 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
373
374 // Request empty post /challengeproof
375 let data = "";
376 let request = Request::builder()
377 .method("POST")
378 .uri("/challengeproof")
379 .body(Body::from(data))
380 .unwrap();
381 let _ = handle(request, challenge_state.clone(), resp_tx.clone())
382 .map(|res| {
383 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
384 res.into_body()
385 .concat2()
386 .map(|chunk| {
387 assert!(String::from_utf8_lossy(&chunk).contains("bad-json-data"));
388 })
389 .wait()
390 })
391 .wait();
392 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
393
394 // Request good post /challengeproof
395 let secret_key = SecretKey::from_slice(&[0xaa; 32]).unwrap();
396 let secp = Secp256k1::new();
397 let sig = secp.sign(&Message::from_slice(&serialize(&chl_hash)).unwrap(), &secret_key);
398 let data = format!(
399 r#"
400 {{
401 "txid": "{}",
402 "pubkey": "{}",
403 "hash": "{}",
404 "sig": "{}"
405 }}"#,
406 bid_txid,
407 bid_pubkey,
408 chl_hash,
409 sig.serialize_der().to_hex()
410 );
411 let request = Request::builder()
412 .method("POST")
413 .uri("/challengeproof")
414 .body(Body::from(data))
415 .unwrap();
416 let _ = handle(request, challenge_state.clone(), resp_tx.clone())
417 .map(|res| {
418 assert_eq!(res.status(), StatusCode::OK);
419 res.into_body()
420 .concat2()
421 .map(|chunk| {
422 assert_eq!("", String::from_utf8_lossy(&chunk));
423 })
424 .wait()
425 })
426 .wait();
427 assert!(
428 resp_rx.try_recv()
429 == Ok(ChallengeResponse(
430 chl_hash,
431 Bid {
432 txid: bid_txid,
433 pubkey: bid_pubkey,
434 },
435 ))
436 ); // check receiver not empty
437 }
438
439 #[test]
440 fn handle_challengeproof_test() {
441 let (resp_tx, resp_rx): (Sender<ChallengeResponse>, Receiver<ChallengeResponse>) = channel();
442
443 let chl_hash = gen_dummy_hash(8);
444 let _challenge_state = gen_challenge_state(&gen_dummy_hash(1), &chl_hash);
445 let bid_txid = _challenge_state.bids.iter().next().unwrap().txid;
446 let bid_pubkey = _challenge_state.bids.iter().next().unwrap().pubkey;
447 let challenge_state = Arc::new(Mutex::new(_challenge_state));
448
449 // Request body data empty
450 let data = "";
451 let request = Request::new(Body::from(data));
452 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
453 .map(|res| {
454 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
455 res.into_body()
456 .concat2()
457 .map(|chunk| {
458 assert!(String::from_utf8_lossy(&chunk).contains("bad-json-data"));
459 })
460 .wait()
461 })
462 .wait();
463 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
464
465 // Bad json data on request body (extra comma)
466 let data = r#"
467 {
468 "txid": "1234567890000000000000000000000000000000000000000000000000000000",
469 }"#;
470 let request = Request::new(Body::from(data));
471 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
472 .map(|res| {
473 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
474 res.into_body()
475 .concat2()
476 .map(|chunk| {
477 assert!(String::from_utf8_lossy(&chunk).contains("bad-json-data"));
478 })
479 .wait()
480 })
481 .wait();
482 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
483
484 // Missing proof data on request body
485 let data = r#"
486 {
487 "txid": "1234567890000000000000000000000000000000000000000000000000000000"
488 }"#;
489 let request = Request::new(Body::from(data));
490 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
491 .map(|res| {
492 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
493 res.into_body()
494 .concat2()
495 .map(|chunk| {
496 assert!(String::from_utf8_lossy(&chunk).contains("bad-proof-data"));
497 })
498 .wait()
499 })
500 .wait();
501 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
502
503 // Bad proof data on request body (invalid pubkey)
504 let data = r#"
505 {
506 "txid": "1234567890000000000000000000000000000000000000000000000000000000",
507 "pubkey": "3356190524d52d7e94e1bd43e8f23778e585a4fe1f275e65a06fa5ceedb67d2f3",
508 "hash": "0404040404040404040404040404040404040404040404040404040404040404",
509 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
510 }"#;
511 let request = Request::new(Body::from(data));
512 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
513 .map(|res| {
514 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
515 res.into_body()
516 .concat2()
517 .map(|chunk| {
518 assert!(String::from_utf8_lossy(&chunk).contains("bad-proof-data"));
519 })
520 .wait()
521 })
522 .wait();
523 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
524
525 // No active challenge (hash is None) so request rejected
526 challenge_state.lock().unwrap().latest_challenge = None;
527 let data = r#"
528 {
529 "txid": "0000000000000000000000000000000000000000000000000000000000000000",
530 "pubkey": "03356190524d52d7e94e1bd43e8f23778e585a4fe1f275e65a06fa5ceedb67d111",
531 "hash": "0404040404040404040404040404040404040404040404040404040404040404",
532 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
533 }"#;
534 let request = Request::new(Body::from(data));
535 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
536 .map(|res| {
537 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
538 res.into_body()
539 .concat2()
540 .map(|chunk| {
541 assert!(String::from_utf8_lossy(&chunk).contains("no-active-challenge"));
542 })
543 .wait()
544 })
545 .wait();
546 challenge_state.lock().unwrap().latest_challenge = Some(chl_hash);
547 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
548
549 // Invalid bid on request body (txid does not exist)
550 let data = r#"
551 {
552 "txid": "0000000000000000000000000000000000000000000000000000000000000000",
553 "pubkey": "03356190524d52d7e94e1bd43e8f23778e585a4fe1f275e65a06fa5ceedb67d111",
554 "hash": "0404040404040404040404040404040404040404040404040404040404040404",
555 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
556 }"#;
557 let request = Request::new(Body::from(data));
558 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
559 .map(|res| {
560 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
561 res.into_body()
562 .concat2()
563 .map(|chunk| {
564 assert!(String::from_utf8_lossy(&chunk).contains("bad-bid"));
565 })
566 .wait()
567 })
568 .wait();
569 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
570
571 // Invalid bid on request body (pubkey does not exist)
572 let data = format!(r#"
573 {{
574 "txid": "{}",
575 "pubkey": "03356190524d52d7e94e1bd43e8f23778e585a4fe1f275e65a06fa5ceedb67d111",
576 "hash": "0404040404040404040404040404040404040404040404040404040404040404",
577 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
578 }}"#, bid_txid);
579 let request = Request::new(Body::from(data));
580 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
581 .map(|res| {
582 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
583 res.into_body()
584 .concat2()
585 .map(|chunk| {
586 assert!(String::from_utf8_lossy(&chunk).contains("bad-bid"));
587 })
588 .wait()
589 })
590 .wait();
591 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
592
593 // Request send for an invalid / out of date challenge hash
594 let data = format!(r#"
595 {{
596 "txid": "{}",
597 "pubkey": "{}",
598 "hash": "0404040404040404040404040404040404040404040404040404040404040404",
599 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
600 }}"#, bid_txid, bid_pubkey);
601 let request = Request::new(Body::from(data));
602 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
603 .map(|res| {
604 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
605 res.into_body()
606 .concat2()
607 .map(|chunk| {
608 assert!(String::from_utf8_lossy(&chunk).contains("bad-hash"));
609 })
610 .wait()
611 })
612 .wait();
613 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
614
615 // Request sent an invalid sig for the correct bid and challenge hash
616 let data = format!(r#"
617 {{
618 "txid": "{}",
619 "pubkey": "{}",
620 "hash": "{}",
621 "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c"
622 }}"#, bid_txid, bid_pubkey, chl_hash);
623 let request = Request::new(Body::from(data));
624 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
625 .map(|res| {
626 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
627 res.into_body()
628 .concat2()
629 .map(|chunk| {
630 assert!(String::from_utf8_lossy(&chunk).contains("bad-sig"));
631 })
632 .wait()
633 })
634 .wait();
635 assert!(resp_rx.try_recv() == Err(TryRecvError::Empty)); // check receiver empty
636
637 // Correct sig sent in the request body for bid and active challenge
638 let secret_key = SecretKey::from_slice(&[0xaa; 32]).unwrap();
639 let secp = Secp256k1::new();
640 let sig = secp.sign(&Message::from_slice(&serialize(&chl_hash)).unwrap(), &secret_key);
641 let data = format!(
642 r#"
643 {{
644 "txid": "{}",
645 "pubkey": "{}",
646 "hash": "{}",
647 "sig": "{}"
648 }}"#,
649 bid_txid,
650 bid_pubkey,
651 chl_hash,
652 sig.serialize_der().to_hex()
653 );
654 let request = Request::new(Body::from(data));
655 let _ = handle_challengeproof(request, challenge_state.clone(), resp_tx.clone())
656 .map(|res| {
657 assert_eq!(res.status(), StatusCode::OK);
658 res.into_body()
659 .concat2()
660 .map(|chunk| {
661 assert!(String::from_utf8_lossy(&chunk) == "");
662 })
663 .wait()
664 })
665 .wait();
666 assert!(
667 resp_rx.try_recv()
668 == Ok(ChallengeResponse(
669 chl_hash,
670 Bid {
671 txid: bid_txid,
672 pubkey: bid_pubkey,
673 },
674 ))
675 ); // check receiver not empty
676 }
677}