From acb243a0a2b60663709bffc8ed76083b72588a71 Mon Sep 17 00:00:00 2001 From: rishflab Date: Wed, 5 May 2021 13:31:07 +1000 Subject: [PATCH] Add protocol state machine --- monero-adaptor/src/alice.rs | 153 ++++++++++++++ monero-adaptor/src/bob.rs | 164 +++++++++++++++ monero-adaptor/src/lib.rs | 286 +-------------------------- swap/src/asb/tracing.rs | 1 + swap/src/lib.rs | 1 + swap/src/xmr_first_protocol.rs | 6 + swap/src/xmr_first_protocol/alice.rs | 133 +++++++++++++ swap/src/xmr_first_protocol/bob.rs | 127 ++++++++++++ 8 files changed, 590 insertions(+), 281 deletions(-) create mode 100644 monero-adaptor/src/alice.rs create mode 100644 monero-adaptor/src/bob.rs create mode 100644 swap/src/xmr_first_protocol.rs create mode 100644 swap/src/xmr_first_protocol/alice.rs create mode 100644 swap/src/xmr_first_protocol/bob.rs diff --git a/monero-adaptor/src/alice.rs b/monero-adaptor/src/alice.rs new file mode 100644 index 00000000..93e3fb3d --- /dev/null +++ b/monero-adaptor/src/alice.rs @@ -0,0 +1,153 @@ +use crate::{ + final_challenge, AdaptorSignature, Commitment, DleqProof, Message0, Message1, Message2, + Message3, Opening, RING_SIZE, +}; +use anyhow::Result; +use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; +use curve25519_dalek::edwards::EdwardsPoint; +use curve25519_dalek::scalar::Scalar; +use hash_edwards_to_edwards::hash_point_to_point; +use rand::rngs::OsRng; + +pub enum AliceState { + WaitingForCommitment(Alice0), + WaitingForScalar(Alice1), + PublishXmr, + RedeemBtc, + PunishXmr, +} + +pub struct Alice0 { + // secret index is always 0 + ring: [EdwardsPoint; RING_SIZE], + fake_responses: [Scalar; RING_SIZE - 1], + msg: [u8; 32], + // encryption key + R_a: EdwardsPoint, + // R'a = r_a*H_p(p_k) where p_k is the signing public key + R_prime_a: EdwardsPoint, + // this is not s_a cos of something to with one-time-address?? + s_prime_a: Scalar, + // secret value: + alpha_a: Scalar, + H_p_pk: EdwardsPoint, + I_a: EdwardsPoint, + I_hat_a: EdwardsPoint, + T_a: EdwardsPoint, +} + +impl Alice0 { + pub fn new( + ring: [EdwardsPoint; RING_SIZE], + msg: [u8; 32], + R_a: EdwardsPoint, + R_prime_a: EdwardsPoint, + s_prime_a: Scalar, + ) -> Result { + let mut fake_responses = [Scalar::zero(); RING_SIZE - 1]; + for response in fake_responses.iter_mut().take(RING_SIZE - 1) { + *response = Scalar::random(&mut OsRng); + } + let alpha_a = Scalar::random(&mut OsRng); + + let p_k = ring[0]; + let H_p_pk = hash_point_to_point(p_k); + + let I_a = s_prime_a * H_p_pk; + let I_hat_a = alpha_a * H_p_pk; + let T_a = alpha_a * ED25519_BASEPOINT_POINT; + + Ok(Alice0 { + ring, + fake_responses, + msg, + R_a, + R_prime_a, + s_prime_a, + alpha_a, + H_p_pk, + I_a, + I_hat_a, + T_a, + }) + } + + pub fn next_message(&self) -> Message0 { + Message0 { + pi_a: DleqProof::new( + ED25519_BASEPOINT_POINT, + self.T_a, + self.H_p_pk, + self.I_hat_a, + self.alpha_a, + ), + c_a: Commitment::new(self.fake_responses, self.I_a, self.I_hat_a, self.T_a), + } + } + + pub fn receive(self, msg: Message1) -> Result { + msg.pi_b + .verify(ED25519_BASEPOINT_POINT, msg.T_b, self.H_p_pk, msg.I_hat_b)?; + + let (h_last, h_0) = final_challenge( + self.fake_responses, + self.ring, + self.T_a, + msg.T_b, + self.R_a, + self.I_hat_a, + msg.I_hat_b, + self.R_prime_a, + self.I_a, + msg.I_b, + self.msg, + )?; + + let s_0_a = self.alpha_a - h_last * self.s_prime_a; + + Ok(Alice1 { + fake_responses: self.fake_responses, + h_0, + I_b: msg.I_b, + s_0_a, + I_a: self.I_a, + I_hat_a: self.I_hat_a, + T_a: self.T_a, + }) + } +} + +pub struct Alice1 { + fake_responses: [Scalar; RING_SIZE - 1], + I_a: EdwardsPoint, + I_hat_a: EdwardsPoint, + T_a: EdwardsPoint, + h_0: Scalar, + I_b: EdwardsPoint, + s_0_a: Scalar, +} + +impl Alice1 { + pub fn next_message(&self) -> Message2 { + Message2 { + d_a: Opening::new(self.fake_responses, self.I_a, self.I_hat_a, self.T_a), + s_0_a: self.s_0_a, + } + } + + pub fn receive(self, msg: Message3) -> Alice2 { + let adaptor_sig = AdaptorSignature { + s_0_a: self.s_0_a, + s_0_b: msg.s_0_b, + fake_responses: self.fake_responses, + h_0: self.h_0, + I: self.I_a + self.I_b, + }; + + Alice2 { adaptor_sig } + } +} + +pub struct Alice2 { + pub adaptor_sig: AdaptorSignature, +} diff --git a/monero-adaptor/src/bob.rs b/monero-adaptor/src/bob.rs new file mode 100644 index 00000000..72186459 --- /dev/null +++ b/monero-adaptor/src/bob.rs @@ -0,0 +1,164 @@ +use crate::{ + final_challenge, AdaptorSignature, Commitment, DleqProof, Message0, Message1, Message2, + Message3, RING_SIZE, +}; +use anyhow::Result; +use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; +use curve25519_dalek::edwards::EdwardsPoint; +use curve25519_dalek::scalar::Scalar; +use hash_edwards_to_edwards::hash_point_to_point; +use rand::rngs::OsRng; + +pub enum BobState { + WaitingForCommitment(Bob0), + WaitingForOpening(Bob1), + PublishXmr, + RedeemBtc, + PunishXmr, +} + +pub struct Bob0 { + // secret index is always 0 + ring: [EdwardsPoint; RING_SIZE], + msg: [u8; 32], + // encryption key + R_a: EdwardsPoint, + // R'a = r_a*H_p(p_k) where p_k is the signing public key + R_prime_a: EdwardsPoint, + s_b: Scalar, + // secret value: + alpha_b: Scalar, + H_p_pk: EdwardsPoint, + I_b: EdwardsPoint, + I_hat_b: EdwardsPoint, + T_b: EdwardsPoint, +} + +impl Bob0 { + pub fn new( + ring: [EdwardsPoint; RING_SIZE], + msg: [u8; 32], + R_a: EdwardsPoint, + R_prime_a: EdwardsPoint, + s_b: Scalar, + ) -> Result { + let alpha_b = Scalar::random(&mut OsRng); + + let p_k = ring[0]; + let H_p_pk = hash_point_to_point(p_k); + + let I_b = s_b * H_p_pk; + let I_hat_b = alpha_b * H_p_pk; + let T_b = alpha_b * ED25519_BASEPOINT_POINT; + + Ok(Bob0 { + ring, + msg, + R_a, + R_prime_a, + s_b, + alpha_b, + H_p_pk, + I_b, + I_hat_b, + T_b, + }) + } + + pub fn receive(self, msg: Message0) -> Bob1 { + Bob1 { + ring: self.ring, + msg: self.msg, + R_a: self.R_a, + R_prime_a: self.R_prime_a, + s_b: self.s_b, + alpha_b: self.alpha_b, + H_p_pk: self.H_p_pk, + I_b: self.I_b, + I_hat_b: self.I_hat_b, + T_b: self.T_b, + pi_a: msg.pi_a, + c_a: msg.c_a, + } + } +} + +pub struct Bob1 { + // secret index is always 0 + ring: [EdwardsPoint; RING_SIZE], + msg: [u8; 32], + // encryption key + R_a: EdwardsPoint, + // R'a = r_a*H_p(p_k) where p_k is the signing public key + R_prime_a: EdwardsPoint, + s_b: Scalar, + // secret value: + alpha_b: Scalar, + H_p_pk: EdwardsPoint, + I_b: EdwardsPoint, + I_hat_b: EdwardsPoint, + T_b: EdwardsPoint, + pi_a: DleqProof, + c_a: Commitment, +} + +impl Bob1 { + pub fn next_message(&self) -> Message1 { + Message1 { + I_b: self.I_b, + T_b: self.T_b, + I_hat_b: self.I_hat_b, + pi_b: DleqProof::new( + ED25519_BASEPOINT_POINT, + self.T_b, + self.H_p_pk, + self.I_hat_b, + self.alpha_b, + ), + } + } + + pub fn receive(self, msg: Message2) -> Result { + let (fake_responses, I_a, I_hat_a, T_a) = msg.d_a.open(self.c_a)?; + + self.pi_a + .verify(ED25519_BASEPOINT_POINT, T_a, self.H_p_pk, I_hat_a)?; + + let (h_last, h_0) = final_challenge( + fake_responses, + self.ring, + T_a, + self.T_b, + self.R_a, + I_hat_a, + self.I_hat_b, + self.R_prime_a, + I_a, + self.I_b, + self.msg, + )?; + + let s_0_b = self.alpha_b - h_last * self.s_b; + + let adaptor_sig = AdaptorSignature { + s_0_a: msg.s_0_a, + s_0_b, + fake_responses, + h_0, + I: I_a + self.I_b, + }; + + Ok(Bob2 { s_0_b, adaptor_sig }) + } +} + +pub struct Bob2 { + s_0_b: Scalar, + pub adaptor_sig: AdaptorSignature, +} + +impl Bob2 { + pub fn next_message(&self) -> Message3 { + Message3 { s_0_b: self.s_0_b } + } +} diff --git a/monero-adaptor/src/lib.rs b/monero-adaptor/src/lib.rs index 4728be53..26e69e65 100644 --- a/monero-adaptor/src/lib.rs +++ b/monero-adaptor/src/lib.rs @@ -11,6 +11,9 @@ use rand::rngs::OsRng; use std::convert::TryInto; use tiny_keccak::{Hasher, Keccak}; +mod alice; +mod bob; + const RING_SIZE: usize = 11; const DOMAIN_TAG: &str = "CSLAG_c"; @@ -154,287 +157,6 @@ impl Signature { } } -pub struct Alice0 { - // secret index is always 0 - ring: [EdwardsPoint; RING_SIZE], - fake_responses: [Scalar; RING_SIZE - 1], - msg: [u8; 32], - // encryption key - R_a: EdwardsPoint, - // R'a = r_a*H_p(p_k) where p_k is the signing public key - R_prime_a: EdwardsPoint, - // this is not s_a cos of something to with one-time-address?? - s_prime_a: Scalar, - // secret value: - alpha_a: Scalar, - H_p_pk: EdwardsPoint, - I_a: EdwardsPoint, - I_hat_a: EdwardsPoint, - T_a: EdwardsPoint, -} - -impl Alice0 { - pub fn new( - ring: [EdwardsPoint; RING_SIZE], - msg: [u8; 32], - R_a: EdwardsPoint, - R_prime_a: EdwardsPoint, - s_prime_a: Scalar, - ) -> Result { - let mut fake_responses = [Scalar::zero(); RING_SIZE - 1]; - for response in fake_responses.iter_mut().take(RING_SIZE - 1) { - *response = Scalar::random(&mut OsRng); - } - let alpha_a = Scalar::random(&mut OsRng); - - let p_k = ring[0]; - let H_p_pk = hash_point_to_point(p_k); - - let I_a = s_prime_a * H_p_pk; - let I_hat_a = alpha_a * H_p_pk; - let T_a = alpha_a * ED25519_BASEPOINT_POINT; - - Ok(Alice0 { - ring, - fake_responses, - msg, - R_a, - R_prime_a, - s_prime_a, - alpha_a, - H_p_pk, - I_a, - I_hat_a, - T_a, - }) - } - - pub fn next_message(&self) -> Message0 { - Message0 { - pi_a: DleqProof::new( - ED25519_BASEPOINT_POINT, - self.T_a, - self.H_p_pk, - self.I_hat_a, - self.alpha_a, - ), - c_a: Commitment::new(self.fake_responses, self.I_a, self.I_hat_a, self.T_a), - } - } - - pub fn receive(self, msg: Message1) -> Result { - msg.pi_b - .verify(ED25519_BASEPOINT_POINT, msg.T_b, self.H_p_pk, msg.I_hat_b)?; - - let (h_last, h_0) = final_challenge( - self.fake_responses, - self.ring, - self.T_a, - msg.T_b, - self.R_a, - self.I_hat_a, - msg.I_hat_b, - self.R_prime_a, - self.I_a, - msg.I_b, - self.msg, - )?; - - let s_0_a = self.alpha_a - h_last * self.s_prime_a; - - Ok(Alice1 { - fake_responses: self.fake_responses, - h_0, - I_b: msg.I_b, - s_0_a, - I_a: self.I_a, - I_hat_a: self.I_hat_a, - T_a: self.T_a, - }) - } -} - -pub struct Alice1 { - fake_responses: [Scalar; RING_SIZE - 1], - I_a: EdwardsPoint, - I_hat_a: EdwardsPoint, - T_a: EdwardsPoint, - h_0: Scalar, - I_b: EdwardsPoint, - s_0_a: Scalar, -} - -impl Alice1 { - pub fn next_message(&self) -> Message2 { - Message2 { - d_a: Opening::new(self.fake_responses, self.I_a, self.I_hat_a, self.T_a), - s_0_a: self.s_0_a, - } - } - - pub fn receive(self, msg: Message3) -> Alice2 { - let adaptor_sig = AdaptorSignature { - s_0_a: self.s_0_a, - s_0_b: msg.s_0_b, - fake_responses: self.fake_responses, - h_0: self.h_0, - I: self.I_a + self.I_b, - }; - - Alice2 { adaptor_sig } - } -} - -pub struct Alice2 { - pub adaptor_sig: AdaptorSignature, -} - -pub struct Bob0 { - // secret index is always 0 - ring: [EdwardsPoint; RING_SIZE], - msg: [u8; 32], - // encryption key - R_a: EdwardsPoint, - // R'a = r_a*H_p(p_k) where p_k is the signing public key - R_prime_a: EdwardsPoint, - s_b: Scalar, - // secret value: - alpha_b: Scalar, - H_p_pk: EdwardsPoint, - I_b: EdwardsPoint, - I_hat_b: EdwardsPoint, - T_b: EdwardsPoint, -} - -impl Bob0 { - pub fn new( - ring: [EdwardsPoint; RING_SIZE], - msg: [u8; 32], - R_a: EdwardsPoint, - R_prime_a: EdwardsPoint, - s_b: Scalar, - ) -> Result { - let alpha_b = Scalar::random(&mut OsRng); - - let p_k = ring[0]; - let H_p_pk = hash_point_to_point(p_k); - - let I_b = s_b * H_p_pk; - let I_hat_b = alpha_b * H_p_pk; - let T_b = alpha_b * ED25519_BASEPOINT_POINT; - - Ok(Bob0 { - ring, - msg, - R_a, - R_prime_a, - s_b, - alpha_b, - H_p_pk, - I_b, - I_hat_b, - T_b, - }) - } - - pub fn receive(self, msg: Message0) -> Bob1 { - Bob1 { - ring: self.ring, - msg: self.msg, - R_a: self.R_a, - R_prime_a: self.R_prime_a, - s_b: self.s_b, - alpha_b: self.alpha_b, - H_p_pk: self.H_p_pk, - I_b: self.I_b, - I_hat_b: self.I_hat_b, - T_b: self.T_b, - pi_a: msg.pi_a, - c_a: msg.c_a, - } - } -} - -pub struct Bob1 { - // secret index is always 0 - ring: [EdwardsPoint; RING_SIZE], - msg: [u8; 32], - // encryption key - R_a: EdwardsPoint, - // R'a = r_a*H_p(p_k) where p_k is the signing public key - R_prime_a: EdwardsPoint, - s_b: Scalar, - // secret value: - alpha_b: Scalar, - H_p_pk: EdwardsPoint, - I_b: EdwardsPoint, - I_hat_b: EdwardsPoint, - T_b: EdwardsPoint, - pi_a: DleqProof, - c_a: Commitment, -} - -impl Bob1 { - pub fn next_message(&self) -> Message1 { - Message1 { - I_b: self.I_b, - T_b: self.T_b, - I_hat_b: self.I_hat_b, - pi_b: DleqProof::new( - ED25519_BASEPOINT_POINT, - self.T_b, - self.H_p_pk, - self.I_hat_b, - self.alpha_b, - ), - } - } - - pub fn receive(self, msg: Message2) -> Result { - let (fake_responses, I_a, I_hat_a, T_a) = msg.d_a.open(self.c_a)?; - - self.pi_a - .verify(ED25519_BASEPOINT_POINT, T_a, self.H_p_pk, I_hat_a)?; - - let (h_last, h_0) = final_challenge( - fake_responses, - self.ring, - T_a, - self.T_b, - self.R_a, - I_hat_a, - self.I_hat_b, - self.R_prime_a, - I_a, - self.I_b, - self.msg, - )?; - - let s_0_b = self.alpha_b - h_last * self.s_b; - - let adaptor_sig = AdaptorSignature { - s_0_a: msg.s_0_a, - s_0_b, - fake_responses, - h_0, - I: I_a + self.I_b, - }; - - Ok(Bob2 { s_0_b, adaptor_sig }) - } -} - -pub struct Bob2 { - s_0_b: Scalar, - pub adaptor_sig: AdaptorSignature, -} - -impl Bob2 { - pub fn next_message(&self) -> Message3 { - Message3 { s_0_b: self.s_0_b } - } -} - struct DleqProof { s: Scalar, c: Scalar, @@ -603,6 +325,8 @@ pub struct Message3 { #[cfg(test)] mod tests { use super::*; + use crate::alice::Alice0; + use crate::bob::Bob0; #[test] fn sign_and_verify_success() { diff --git a/swap/src/asb/tracing.rs b/swap/src/asb/tracing.rs index dd27194c..3e7e414f 100644 --- a/swap/src/asb/tracing.rs +++ b/swap/src/asb/tracing.rs @@ -21,6 +21,7 @@ pub fn init(level: LevelFilter) -> Result<()> { builder.init(); } + tracing tracing::info!("Initialized tracing with level: {}", level); Ok(()) diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 4af15cc1..3bf4fdbf 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -27,5 +27,6 @@ pub mod monero; pub mod network; pub mod protocol; pub mod seed; +mod xmr_first_protocol; mod monero_ext; diff --git a/swap/src/xmr_first_protocol.rs b/swap/src/xmr_first_protocol.rs new file mode 100644 index 00000000..f180ca78 --- /dev/null +++ b/swap/src/xmr_first_protocol.rs @@ -0,0 +1,6 @@ +mod alice; +mod bob; + +pub trait Persist { + fn persist(&self); +} diff --git a/swap/src/xmr_first_protocol/alice.rs b/swap/src/xmr_first_protocol/alice.rs new file mode 100644 index 00000000..6c584822 --- /dev/null +++ b/swap/src/xmr_first_protocol/alice.rs @@ -0,0 +1,133 @@ +use crate::xmr_first_protocol::Persist; +use std::collections::VecDeque; +use std::task::Poll; + +pub struct StateMachine { + state: State, + actions: VecDeque, + events: VecDeque, +} + +impl StateMachine { + fn inject_event(&mut self, event: Event) { + match self.state { + State::WatchingForBtcLock => match event { + Event::BtcLockSeenInMempool => { + self.actions.push_back(Action::SignAndBroadcastBtcRedeem); + self.actions.push_back(Action::WatchForXmrRedeem); + self.state = State::WatchingForXmrRedeem; + } + Event::BtcLockTimeoutElapsed => { + self.actions.push_back(Action::BroadcastXmrRefund); + self.state = State::Aborted; + } + _ => {} + }, + State::WatchingForXmrRedeem => match event { + Event::T2Elapsed => { + self.actions.push_back(Action::BroadcastXmrRefund); + self.actions.push_back(Action::SignAndBroadcastBtcPunish); + self.state = State::Punished; + } + Event::XmrRedeemSeenInMempool => { + self.actions.push_back(Action::SignAndBroadcastBtcPunish); + self.state = State::Success; + } + _ => {} + }, + // State::WaitingForT2 => match event { + // Event::T2Elapsed => { + // self.actions.push_back(Action::SignAndBroadcastBtcPunish); + // self.actions.push_back(Action::BroadcastXmrRefund); + // self.state = State::Success; + // } + // _ => {} + // }, + _ => {} + } + } + + fn poll(&mut self) -> Poll { + if let Some(action) = self.actions.pop_front() { + Poll::Ready(action) + } else { + Poll::Pending + } + } +} + +impl Persist for Event { + fn persist(&self) { + todo!() + } +} + +#[derive(PartialEq, Debug)] +pub enum State { + WatchingForBtcLock, + WatchingForXmrRedeem, + // WaitingForT2, + Punished, + Success, + Aborted, +} + +pub enum Event { + BtcLockSeenInMempool, + T2Elapsed, + BtcLockTimeoutElapsed, + XmrRedeemSeenInMempool, +} + +// These actions should not fail (are retried until successful) and should be +// idempotent This allows us to greatly simplify the state machine +pub enum Action { + WatchForXmrRedeem, + SignAndBroadcastBtcPunish, + SignAndBroadcastBtcRedeem, + BroadcastXmrRefund, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn happy_path() { + let mut state_machine = StateMachine { + state: State::WatchingForBtcLock, + actions: Default::default(), + events: Default::default(), + }; + state_machine.inject_event(Event::BtcLockSeenInMempool); + assert_eq!(state_machine.poll()) + state_machine.inject_event(Event::XmrRedeemSeenInMempool); + assert_eq!(state_machine.state, State::Success); + } + + + #[test] + fn bob_fails_to_lock_btc() { + let mut state_machine = StateMachine { + state: State::WatchingForBtcLock, + actions: Default::default(), + events: Default::default(), + }; + state_machine.events.push_back(Event::BtcLockTimeoutElapsed); + state_machine.run(); + assert_eq!(state_machine.state, State::Aborted); + } + + #[test] + fn bob_fails_to_redeem_xmr_before_t2() { + let mut state_machine = StateMachine { + state: State::WatchingForBtcLock, + actions: Default::default(), + events: Default::default(), + }; + state_machine.events.push_back(Event::BtcLockSeenInMempool); + state_machine.events.push_back(Event::T2Elapsed); + state_machine.run(); + assert_eq!(state_machine.state, State::Punished); + } +} diff --git a/swap/src/xmr_first_protocol/bob.rs b/swap/src/xmr_first_protocol/bob.rs new file mode 100644 index 00000000..1598a3b7 --- /dev/null +++ b/swap/src/xmr_first_protocol/bob.rs @@ -0,0 +1,127 @@ +use crate::monero::TransferProof; +use crate::xmr_first_protocol::Persist; +use std::collections::VecDeque; + +pub struct StateMachine { + state: State, + actions: VecDeque, + events: VecDeque, +} + +impl StateMachine { + fn next(&mut self, event: Event) { + match self.state { + State::WatchingForXmrLock => match event { + Event::XmrConfirmed => { + self.actions.push_back(Action::SignAndBroadcastBtcLock); + self.state = State::WaitingForBtcRedeem; + } + Event::T1Elapsed => { + self.state = State::Aborted; + } + Event::XmrRefundSeenInMempool => { + self.state = State::Aborted; + } + _ => panic!("unhandled scenario"), + }, + State::WaitingForBtcRedeem => match event { + Event::BtcRedeemSeenInMempool => { + self.actions.push_back(Action::BroadcastXmrRedeem); + self.state = State::Success; + } + Event::T1Elapsed => { + self.actions.push_back(Action::SignAndBroadcastBtcRefund); + self.state = State::Refunded; + } + Event::XmrRefundSeenInMempool => { + self.actions.push_back(Action::SignAndBroadcastBtcRefund); + self.state = State::Refunded; + } + _ => panic!("unhandled scenario"), + }, + _ => {} + } + } + + fn run(&mut self) { + while let Some(event) = self.events.pop_front() { + self.next(event); + } + } +} + +impl Persist for Event { + fn persist(&self) { + todo!() + } +} + +#[derive(PartialEq, Debug)] +pub enum State { + WatchingForXmrLock, + WaitingForBtcRedeem, + Success, + Refunded, + Aborted, +} + +pub enum Event { + // todo: do we simply wait for confirmations are is mempool sufficient? + XmrConfirmed, + // This will contain the s_a allowing bob to build xmr_redeem + BtcRedeemSeenInMempool, + XmrRefundSeenInMempool, + T1Elapsed, +} + +pub enum Action { + SignAndBroadcastBtcLock, + BroadcastXmrRedeem, + SignAndBroadcastBtcRefund, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn happy_path() { + let mut state_machine = StateMachine { + state: State::WatchingForXmrLock, + actions: Default::default(), + events: Default::default(), + }; + state_machine.events.push_back(Event::XmrConfirmed); + state_machine + .events + .push_back(Event::BtcRedeemSeenInMempool); + state_machine.run(); + assert_eq!(state_machine.state, State::Success); + } + + #[test] + fn alice_fails_to_redeem_btc_before_t1() { + let mut state_machine = StateMachine { + state: State::WatchingForXmrLock, + actions: Default::default(), + events: Default::default(), + }; + state_machine.events.push_back(Event::XmrConfirmed); + state_machine.events.push_back(Event::T1Elapsed); + state_machine.run(); + assert_eq!(state_machine.state, State::Refunded); + } + + #[test] + fn alice_tries_to_refund_xmr_after_redeeming_btc() { + let mut state_machine = StateMachine { + state: State::WatchingForXmrLock, + actions: Default::default(), + events: Default::default(), + }; + state_machine.events.push_back(Event::XmrConfirmed); + state_machine.events.push_back(Event::T1Elapsed); + state_machine.run(); + assert_eq!(state_machine.state, State::Refunded); + } +}